diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000000000..a6c57f5fb2ffb --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +*.json 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/.github/workflows/clean-test.yml b/.github/workflows/clean-test.yml index 0f15301d4eed9..b149ae86b991d 100644 --- a/.github/workflows/clean-test.yml +++ b/.github/workflows/clean-test.yml @@ -19,10 +19,6 @@ name: Clean PR checks # Warning: the check_* keys are magic and must consist of the string # "check_" followed by the applicable check name exactly. The # "description" field is only the human-readable label for the input. - 'check_build/AliceO2/O2/o2/macOS': - description: build/AliceO2/O2/o2/macOS - type: boolean - default: true 'check_build/AliceO2/O2/o2/macOS-arm': description: build/AliceO2/O2/o2/macOS-arm type: boolean @@ -31,8 +27,8 @@ name: Clean PR checks description: build/O2/fullCI type: boolean default: true - 'check_build/O2/o2-dataflow-cs8': - description: build/O2/o2-dataflow-cs8 + 'check_build/O2/o2-dataflow-slc9': + description: build/O2/o2-dataflow-slc9 type: boolean default: true 'check_build/O2/o2/aarch64': diff --git a/.github/workflows/code-transformations.yml b/.github/workflows/code-transformations.yml index bfc60fdfcbe44..35493afda94f5 100644 --- a/.github/workflows/code-transformations.yml +++ b/.github/workflows/code-transformations.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/datamodel-doc.yml b/.github/workflows/datamodel-doc.yml index 51789c9697196..3ba015631aec6 100644 --- a/.github/workflows/datamodel-doc.yml +++ b/.github/workflows/datamodel-doc.yml @@ -10,20 +10,20 @@ jobs: steps: - name: Checkout O2 - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: O2 persist-credentials: false - name: Checkout O2Physics - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: AliceO2Group/O2Physics path: O2Physics persist-credentials: false - name: Checkout documentation - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: AliceO2Group/analysis-framework path: analysis-framework @@ -40,7 +40,7 @@ jobs: git checkout -B auto-datamodel-doc - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index ae867739eba62..b1dbaf122b342 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -13,7 +13,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y doxygen doxygen-doc doxygen-latex doxygen-gui graphviz cmake - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: "dev" persist-credentials: false diff --git a/.github/workflows/first-timer.yml b/.github/workflows/first-timer.yml index 20b7ee6a070a8..54334d109bd49 100644 --- a/.github/workflows/first-timer.yml +++ b/.github/workflows/first-timer.yml @@ -8,7 +8,7 @@ jobs: nag_first_timer: runs-on: ubuntu-latest steps: - - uses: actions/first-interaction@v1 + - uses: actions/first-interaction@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: 'This seems to be your first PR. You will need a positive review in order for tests to start.' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a401af1ba39e2..2f692527ea5ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: branch=$(echo ${{ github.event.inputs.tag }}-patches | tr . - | sed -e's/-[0-9]*-patches$/-patches/') EOF id: decide_release_branch - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: "dev" - name: Tag branch (or create one before tagging if does not exists) diff --git a/.github/workflows/reports.yml b/.github/workflows/reports.yml index cadb920fa022f..5a04e56382fb3 100644 --- a/.github/workflows/reports.yml +++ b/.github/workflows/reports.yml @@ -17,12 +17,12 @@ jobs: if: github.repository == 'AliceO2Group/AliceO2' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python 3.10 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.10' - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Configure pip caching with: path: ~/.cache/pip diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b1be426cdfc77..23f454aaca950 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-pr-message: 'This PR did not have any update in the last 30 days. Is it still needed? Unless further action in will be closed in 5 days.' diff --git a/.skills/build-software-stack/SKILL.md b/.skills/build-software-stack/SKILL.md new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/.skills/build-software-stack/SKILL.md @@ -0,0 +1 @@ + diff --git a/.skills/create-a-new-file/SKILL.md b/.skills/create-a-new-file/SKILL.md new file mode 100644 index 0000000000000..a7af739d49894 --- /dev/null +++ b/.skills/create-a-new-file/SKILL.md @@ -0,0 +1,20 @@ +--- +name: create-a-new-file +description: describes how to create a new file +--- + +## Copyright statements + +The copyright statement for ALICE / O2 is found in ./o2-copyright-statement.md. It should be at the beginning of +the new file using the proper commenting syntax for the given programming language. For example in C++ it should be commented via +multiline comments: + +``` C++ +// Copyright 2019- CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// ... +``` + +The only part which needs to be adapted by you is the `` which you need to replace with the actual current year. + + diff --git a/Framework/Foundation/src/Traits.cxx b/.skills/create-a-new-file/o2-copyright-statement.md similarity index 86% rename from Framework/Foundation/src/Traits.cxx rename to .skills/create-a-new-file/o2-copyright-statement.md index faff430964e73..0bc4b092e6ba6 100644 --- a/Framework/Foundation/src/Traits.cxx +++ b/.skills/create-a-new-file/o2-copyright-statement.md @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019- 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. // @@ -8,3 +8,4 @@ // 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. + diff --git a/Algorithm/include/Algorithm/BitstreamReader.h b/Algorithm/include/Algorithm/BitstreamReader.h deleted file mode 100644 index 0a112183ab5ef..0000000000000 --- a/Algorithm/include/Algorithm/BitstreamReader.h +++ /dev/null @@ -1,290 +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 BITSTREAMREADER_H -#define BITSTREAMREADER_H - -/// @file BitstreamReader.h -/// @author Matthias Richter -/// @since 2019-06-05 -/// @brief Utility class to provide bitstream access to an underlying resource - -#include -#include - -namespace o2 -{ -namespace algorithm -{ - -/// @class BitStreamReader -/// @brief Utility class to provide bitstream access to an underlying resource -/// -/// Allows to access bits of variable length, supports integral types and also -/// bitsets as target type. At the moment, the access is in direction MSB -> LSB. -/// -/// BitstreamReader reader(start, end); -/// while (reader.good() && not reader.eof()) { -/// // get an 8 bit value from the stream, moves the position -/// uint8_t ivalue; -/// reader.get(ivalue); -/// -/// // get a 13 bit bitset without moving the position -/// std::bitset<13> value; -/// reader.peek(value, value.size()); -/// // e.g. use 7 bits of the data -/// value >>= value.size() - 7; -/// // move position by the specific number of bits -/// reader.seek(7); -/// } -template -class BitstreamReader -{ - public: - using self_type = BitstreamReader; - // for the moment we simply use pointers, but with some traits this can be extended to - // containers - using value_type = BufferType; - using iterator = const value_type*; - static constexpr size_t value_size = sizeof(value_type) * 8; - BitstreamReader() = delete; - BitstreamReader(iterator start, iterator end) - : mStart(start), mEnd(end), mCurrent(mStart), mBitPosition(value_size) - { - } - ~BitstreamReader() = default; - - /// Check reader's state - /// @return true if not in error state - bool good() const - { - return mBitPosition > 0; - } - - /// Indicates end of data - /// @return true if end of resource is reached - bool eof() const - { - return mCurrent == mEnd && mBitPosition > 0; - } - - /// Reset the reader, start over at beginning - void reset() - { - mCurrent = mStart; - mBitPosition = value_size; - } - - /// Get the next N bits without moving the read position - /// if bitlength is smaller than the size of data type, result is aligned to LSB - /// TODO: this also works nicely for bitsets, but then the bitlength has to be specified - /// as template parameter, want to do a specific overload, but needs more work to catch - /// all cases. - /// @param v target variable passed by reference - /// @return number of poked bits - template - size_t peek(T& v) - { - static_assert(N <= sizeof(T) * 8); - return peek(v, N); - } - - /// Get the next n bits without moving the read position - /// if bitlength is smaller than the size of data type, result is aligned to LSB - /// @param v target variable passed by reference - /// @param bitlength number of bits to read - /// @return number of poked bits - template - size_t peek(T& v, size_t bitlength) - { - return peek(v, bitlength); - } - - /// Move read position - /// @param bitlength move count in number of bits - void seek(size_t bitlength) - { - while (good() && bitlength > 0 && mCurrent != mEnd) { - if (bitlength >= mBitPosition) { - bitlength -= mBitPosition; - mBitPosition = 0; - } else { - mBitPosition -= bitlength; - bitlength = 0; - } - if (mBitPosition == 0) { - mCurrent++; - mBitPosition = value_size; - } - } - - if (bitlength > 0) { - mBitPosition = 0; - } - } - - /// Get the next n bits and move the read position - template - T get() - { - T result; - peek(result); - seek(N); - return result; - } - - /// Get the next n and move the read position - template - T get(size_t bitlength = sizeof(T) * 8) - { - T result; - peek(result, bitlength); - seek(bitlength); - return result; - } - - /// @class Bits - /// @brief Helper class to get value of specified type which holds the number used bits - /// - /// The class holds both the extracted value access via peek method and the number of used - /// bits. The reader will be incremented when the object is destroyed. - /// The number of bits can be adjusted by using markUsed method - template - class Bits - { - public: - using field_type = FieldType; - static_assert(N <= sizeof(FieldType) * 8); - Bits() - : mParent(nullptr), mData(0), mLength(0) - { - } - Bits(ParentType* parent, FieldType&& data) - : mParent(parent), mData(std::move(data)), mLength(N) - { - } - Bits(Bits&& other) - : mParent(other.mParent), mData(std::move(other.mData)), mLength(other.mLength) - { - other.mParent = nullptr; - other.mLength = 0; - } - - ~Bits() - { - if (mParent) { - mParent->seek(mLength); - } - } - - auto& operator=(Bits&& other) - { - mParent = other.mParent; - mData = std::move(other.mData); - mLength = other.mLength; - other.mParent = nullptr; - other.mLength = 0; - - return *this; - } - - FieldType& operator*() - { - return mData; - } - - void markUsed(size_t length) - { - mLength = length; - } - - private: - ParentType* mParent; - FieldType mData; - size_t mLength; - }; - - /// Read an integral value from the stream - template ::value, int> = 0> - self_type& operator>>(T& target) - { - target = get(); - return *this; - } - - /// Read a bitstream value from the stream - template - self_type& operator>>(std::bitset& target) - { - target = get, N>(); - return *this; - } - - /// Read a Bits object from the stream - template - self_type& operator>>(Bits& target) - { - T bitfield; - peek(bitfield); - target = std::move(Bits(this, std::move(bitfield))); - return *this; - } - - private: - /// The internal peek method - template - size_t peek(T& result, size_t bitlength) - { - if constexpr (RuntimeCheck) { - // the runtime check is disabled if bitlength is derived at compile time - if (bitlength > sizeof(T) * 8) { - throw std::length_error(std::string("requested bit length ") + std::to_string(bitlength) + " does not fit size of result data type " + std::to_string(sizeof(T) * 8)); - } - } - result = 0; - size_t bitsToWrite = bitlength; - auto current = mCurrent; - auto bitsAvailable = mBitPosition; - while (bitsToWrite > 0 && current != mEnd) { - // extract available bits - value_type mask = ~value_type(0) >> (value_size - bitsAvailable); - if (bitsToWrite >= bitsAvailable) { - T value = (*current & mask) << (bitsToWrite - bitsAvailable); - result |= value; - bitsToWrite -= bitsAvailable; - bitsAvailable = 0; - } else { - value_type value = (*current & mask) >> (bitsAvailable - bitsToWrite); - result |= value; - bitsAvailable -= bitsToWrite; - bitsToWrite = 0; - } - if (bitsAvailable == 0) { - current++; - bitsAvailable = value_size; - } - } - - return bitlength - bitsToWrite; - } - - /// start of resource - iterator mStart; - /// end of resource - iterator mEnd; - /// current position in resource - iterator mCurrent; - /// bit position in current element - size_t mBitPosition; -}; -} // namespace algorithm -} // namespace o2 -#endif diff --git a/Algorithm/test/test_BitstreamReader.cxx b/Algorithm/test/test_BitstreamReader.cxx deleted file mode 100644 index 41e3b47f5f276..0000000000000 --- a/Algorithm/test/test_BitstreamReader.cxx +++ /dev/null @@ -1,121 +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 test_BitstreamReader.cxx -/// @author Matthias Richter -/// @since 2019-06-05 -/// @brief Test program for BitstreamReader utility - -#define BOOST_TEST_MODULE Algorithm BitstreamReader unit test -#define BOOST_TEST_MAIN -#define BOOST_TEST_DYN_LINK -#include -#include -#include -#include -#include -#include -#include "../include/Algorithm/BitstreamReader.h" - -namespace o2 -{ -namespace algorithm -{ - -BOOST_AUTO_TEST_CASE(test_BitstreamReader_basic) -{ - std::array data = {'d', 'e', 'a', 'd', 'b', 'e', 'e', 'f'}; - std::array expected7bit = {0x32, 0x19, 0x2c, 0x16, 0x23, 0x09, 0x4a, 0x65, 0x33, 0x0}; - auto reference = expected7bit.begin(); - constexpr size_t totalBits = data.size() * sizeof(decltype(data)::value_type) * 8; - size_t bitsRead = 0; - - BitstreamReader reader(data.data(), data.data() + data.size()); - while (bitsRead < totalBits) { - BOOST_REQUIRE(reference != expected7bit.end()); - BOOST_CHECK(reader.eof() == false); - uint8_t value; - reader.peek(value); - // we use 7 bits of the data - value >>= 1; - reader.seek(7); - bitsRead += 7; - // in the last call should there is not enough data - BOOST_CHECK(reader.good() == (bitsRead <= totalBits)); - BOOST_REQUIRE(reference != expected7bit.end()); - //std::cout << "value " << (int)value << " expected " << (int)*reference << std::endl; - BOOST_CHECK(value == *reference); - ++reference; - } -} - -BOOST_AUTO_TEST_CASE(test_BitstreamReader_operator) -{ - std::array data = {'d', 'e', 'a', 'd', 'b', 'e', 'e', 'f'}; - std::array expected7bit = {0x32, 0x19, 0x2c, 0x16, 0x23, 0x09, 0x4a, 0x65, 0x33, 0x0}; - auto reference = expected7bit.begin(); - constexpr size_t totalBits = data.size() * sizeof(decltype(data)::value_type) * 8; - size_t bitsRead = 0; - - BitstreamReader reader(data.data(), data.data() + data.size()); - while (bitsRead < totalBits) { - BOOST_REQUIRE(reference != expected7bit.end()); - BOOST_CHECK(reader.eof() == false); - { - decltype(reader)::Bits value; - reader >> value; - // we use 7 bits of the data - *value >>= 1; - value.markUsed(7); - //std::cout << "value " << (int)*value << " expected " << (int)*reference << std::endl; - BOOST_CHECK(*value == *reference); - } - bitsRead += 7; - // in the last call should there is not enough data - BOOST_CHECK(reader.good() == (bitsRead <= totalBits)); - BOOST_REQUIRE(reference != expected7bit.end()); - ++reference; - } -} - -BOOST_AUTO_TEST_CASE(test_BitstreamReader_bitset) -{ - std::array data = {'d', 'e', 'a', 'd', 'b', 'e', 'e', 'f'}; - std::array expected7bit = {0x32, 0x19, 0x2c, 0x16, 0x23, 0x09, 0x4a, 0x65, 0x33, 0x0}; - auto reference = expected7bit.begin(); - constexpr size_t totalBits = data.size() * sizeof(decltype(data)::value_type) * 8; - size_t bitsRead = 0; - - BitstreamReader reader(data.data(), data.data() + data.size()); - while (bitsRead < totalBits) { - BOOST_REQUIRE(reference != expected7bit.end()); - BOOST_CHECK(reader.eof() == false); - std::bitset<13> value; - reader.peek(value, value.size()); - // we use 7 bits of the data - value >>= value.size() - 7; - reader.seek(7); - bitsRead += 7; - // in the last call should there is not enough data - BOOST_CHECK(reader.good() == (bitsRead <= totalBits)); - BOOST_REQUIRE(reference != expected7bit.end()); - BOOST_CHECK_MESSAGE(value.to_ulong() == *reference, std::string("mismatch: value ") << value.to_ulong() << ", expected " << (int)*reference); - ++reference; - } - - reader.reset(); - std::bitset<16> aBitset; - reader >> aBitset; - BOOST_CHECK_MESSAGE(aBitset.to_ulong() == 0x6465, std::string("mismatch: value 0x") << std::hex << aBitset.to_ulong() << ", expected 0x6465"); -} - -} // namespace algorithm -} // namespace o2 diff --git a/CCDB/CMakeLists.txt b/CCDB/CMakeLists.txt index 9436fa37de8e6..691c3311e117c 100644 --- a/CCDB/CMakeLists.txt +++ b/CCDB/CMakeLists.txt @@ -92,6 +92,12 @@ o2_add_test(CcdbDownloader PUBLIC_LINK_LIBRARIES O2::CCDB LABELS ccdb) +o2_add_test(CcdbApi-Headers + SOURCES test/testCcdbApiHeaders.cxx + COMPONENT_NAME ccdb + PUBLIC_LINK_LIBRARIES O2::CCDB + LABELS ccdb) + # extra CcdbApi test which dispatches to CCDBDownloader (tmp until full move done) #o2_add_test_command(NAME CcdbApi-MultiHandle # WORKING_DIRECTORY ${SIMTESTDIR} diff --git a/CCDB/README.md b/CCDB/README.md index e098617cf44e3..1ae5f29dcf0e2 100644 --- a/CCDB/README.md +++ b/CCDB/README.md @@ -13,7 +13,7 @@ in circumstances of reduced or no network connectivity. There are currently 2 different kinds of store/retrieve functions, which we expect to unify in the immediate future: 2. `storeAsTFile/retrieveFromTFile` API serializing a `TObject` in a ROOT `TFile`. -3. A strongly-typed `storeAsTFileAny/retrieveFromTFileAny` API allowing to handle any type T +3. A strongly-typed `storeAsTFileAny/retrieveFromTFileAny` API allowing to handle any type T having a ROOT dictionary. We encourage to use this API by default. ## Central and local instances of the CCDB @@ -37,12 +37,12 @@ api.init("http://ccdb-test.cern.ch:8080"); // or http://localhost:8080 for a loc auto deadpixels = new o2::FOO::DeadPixelMap(); api.storeAsTFileAny(deadpixels, "FOO/DeadPixels", metadata); // read like this (you have to specify the type) -auto deadpixelsback = api.retrieveFromTFileAny("FOO/DeadPixels", metadata); -// read like this to get the headers as well, and thus the metadata attached to the object +auto deadpixelsback = api.retrieveFromTFileAny("FOO/DeadPixels", metadata); +// read like this to get the headers as well, and thus the metadata attached to the object std::map headers; -auto deadpixelsback = api.retrieveFromTFileAny("FOO/DeadPixels", metadata /* constraint the objects retrieved to those matching the metadata */, -1 /* timestamp */, &headers /* the headers attached to the returned object */); +auto deadpixelsback = api.retrieveFromTFileAny("FOO/DeadPixels", metadata /* constraint the objects retrieved to those matching the metadata */, -1 /* timestamp */, &headers /* the headers attached to the returned object */); // finally, use this method to retrieve only the headers (and thus the metadata) -std::map headers = f.api.retrieveHeaders("FOO/DeadPixels", f.metadata); +std::map headers = api.retrieveHeaders("FOO/DeadPixels", metadata); ``` * creating a local snapshot and fetching objects therefrom @@ -85,7 +85,7 @@ user code. This class The class was written for the use-case of transport MC simulation. Typical usage should be like ```c++ -// setup manager once (at start of processing) +// setup manager once (at start of processing) auto& mgr = o2::ccdb::BasicCCDBManager::instance(); mgr.setURL("http://ourccdbserverver.cern.ch"); mgr.setTimestamp(timestamp_which_we_want_to_anchor_to); @@ -111,6 +111,12 @@ This feature is useful to avoid using newer objects if the CCDB is updated in pa In cached mode, the manager can check that local objects are still valid by requiring `mgr.setLocalObjectValidityChecking(true)`, in this case a CCDB query is performed only if the cached object is no longer valid. +If you want the headers/metadata for the object retrieved from the CCDB there is an optional paramater to `BasicCCDBManager::getForTimeStamp`. These headers are also cached (when caching is enabled) and is updated when a CCDB query is sent. +```c++ +std::map headers; +mgr.getForTimeStamp(path, timstamp, metadata, &headers); +``` + ## Future ideas / todo: - [ ] offer improved error handling / exceptions @@ -129,26 +135,26 @@ A few prototypic command line tools are offered. These can be used in scriptable and facilitate the following tasks: 1. Upload and annotate a generic C++ object serialized in a ROOT file - + ```bash o2-ccdb-upload -f myRootFile.root --key histogram1 --path /Detector1/QA/ --meta "Description=Foo;Author=Person1;Uploader=Person2" ``` This will upload the object serialized in `myRootFile.root` under the key `histogram1`. Object will be put to the CCDB path `/Detector1/QA`. For full list of options see `o2-ccdb-upload --help`. - + 2. Download a CCDB object to a local ROOT file (including its meta information) - + ```bash o2-ccdb-downloadccdbfile --path /Detector1/QA/ --dest /tmp/CCDB --timestamp xxx ``` This will download the CCDB object under path given by `--path` to a directory given by `--dest` on the disc. (The final filename will be `/tmp/CCDB/Detector1/QA/snapshot.root` for the moment). All meta-information as well as the information associated to this query will be appended to the file. - + For full list of options see `o2-ccdb-downloadccdbfile --help`. - + 3. Inspect the content of a ROOT file and print summary about type of contained (CCDB) objects and its meta information - + ```bash o2-ccdb-inspectccdbfile filename ``` diff --git a/CCDB/include/CCDB/BasicCCDBManager.h b/CCDB/include/CCDB/BasicCCDBManager.h index 9668097c39473..6ad645a0a893e 100644 --- a/CCDB/include/CCDB/BasicCCDBManager.h +++ b/CCDB/include/CCDB/BasicCCDBManager.h @@ -20,9 +20,12 @@ #include "CommonUtils/NameConf.h" #include "Framework/DataTakingContext.h" #include "Framework/DefaultsHelpers.h" +#include "Framework/ServiceRegistryRef.h" +#include "Framework/DataProcessingStats.h" #include #include #include +#include #include #include #include @@ -52,16 +55,18 @@ 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; int fetches = 0; int failures = 0; + std::map cacheOfHeaders; 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() { @@ -70,6 +75,7 @@ class CCDBManagerInstance uuid = ""; startvalidity = 0; endvalidity = -1; + cacheOfHeaders.clear(); } }; @@ -98,9 +104,9 @@ class CCDBManagerInstance /// query timestamp long getTimestamp() const { return mTimestamp; } - /// retrieve an object of type T from CCDB as stored under path and timestamp + /// retrieve an object of type T from CCDB as stored under path and timestamp. Optional to get the headers. template - T* getForTimeStamp(std::string const& path, long timestamp); + T* getForTimeStamp(std::string const& path, long timestamp, std::map* headers = nullptr); /// retrieve an object of type T from CCDB as stored under path and using the timestamp in the middle of the run template @@ -112,10 +118,7 @@ class CCDBManagerInstance { // TODO: add some error info/handling when failing mMetaData = metaData; - auto obj = getForTimeStamp(path, timestamp); - if (headers) { - *headers = mHeaders; - } + auto obj = getForTimeStamp(path, timestamp, headers); return obj; } @@ -227,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 @@ -235,7 +239,7 @@ class CCDBManagerInstance }; template -T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp) +T* CCDBManagerInstance::getForTimeStamp(std::string const& path, long timestamp, std::map* headers) { mHeaders.clear(); // we clear at the beginning; to allow to retrieve the header information in a subsequent call T* ptr = nullptr; @@ -256,17 +260,36 @@ 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; } } + + if (headers) { + *headers = mHeaders; + } } else { auto& cached = mCache[path]; cached.queries++; if ((!isOnline() && cached.isCacheValid(timestamp)) || (mCheckObjValidityEnabled && cached.isValid(timestamp))) { + // Give back the cached/saved headers + 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, mCreatedNotAfter ? std::to_string(mCreatedNotAfter) : "", mCreatedNotBefore ? std::to_string(mCreatedNotBefore) : ""); + // update the cached headers + for (auto const& h : mHeaders) { + cached.cacheOfHeaders[h.first] = h.second; + } + // return the cached headers + if (headers) { + *headers = cached.cacheOfHeaders; + } + if (ptr) { // new object was shipped, old one (if any) is not valid anymore cached.fetches++; mFetches++; @@ -299,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); } @@ -323,6 +348,15 @@ 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(); + 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; } @@ -374,4 +408,4 @@ class BasicCCDBManager : public CCDBManagerInstance } // namespace o2::ccdb -#endif //O2_BASICCCDBMANAGER_H +#endif // O2_BASICCCDBMANAGER_H diff --git a/CCDB/include/CCDB/CcdbApi.h b/CCDB/include/CCDB/CcdbApi.h index e41f58d5c6da9..4dab11d5972d8 100644 --- a/CCDB/include/CCDB/CcdbApi.h +++ b/CCDB/include/CCDB/CcdbApi.h @@ -281,7 +281,7 @@ class CcdbApi //: public DatabaseInterface * @return: True in case operation successful or false if there was a failure/problem. */ bool retrieveBlob(std::string const& path, std::string const& targetdir, std::map const& metadata, long timestamp, - bool preservePathStructure = true, std::string const& localFileName = "snapshot.root", std::string const& createdNotAfter = "", std::string const& createdNotBefore = "") const; + bool preservePathStructure = true, std::string const& localFileName = "snapshot.root", std::string const& createdNotAfter = "", std::string const& createdNotBefore = "", std::map* headers = nullptr) const; /** * Retrieve the headers of a CCDB entry, if it exists. diff --git a/CCDB/src/BasicCCDBManager.cxx b/CCDB/src/BasicCCDBManager.cxx index bcf88554578c1..53055d0d1231d 100644 --- a/CCDB/src/BasicCCDBManager.cxx +++ b/CCDB/src/BasicCCDBManager.cxx @@ -13,6 +13,8 @@ // Created by Sandro Wenzel on 2019-08-14. // #include "CCDB/BasicCCDBManager.h" +#include "Framework/ServiceRegistryRef.h" +#include "Framework/DataProcessingStats.h" #include #include #include @@ -99,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 27ad14cdf24fa..93a79ad56c477 100644 --- a/CCDB/src/CcdbApi.cxx +++ b/CCDB/src/CcdbApi.cxx @@ -40,13 +40,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include "rapidjson/document.h" #include "rapidjson/writer.h" @@ -117,13 +117,7 @@ CcdbApi::~CcdbApi() void CcdbApi::setUniqueAgentID() { - std::string host = boost::asio::ip::host_name(); - char const* jobID = getenv("ALIEN_PROC_ID"); - if (jobID) { - mUniqueAgentID = fmt::format("{}-{}-{}-{}", host, getCurrentTimestamp() / 1000, o2::utils::Str::getRandomString(6), jobID); - } else { - mUniqueAgentID = fmt::format("{}-{}-{}", host, getCurrentTimestamp() / 1000, o2::utils::Str::getRandomString(6)); - } + mUniqueAgentID = TAlienUserAgent::BasedOnEnvironment().ToString(); } bool CcdbApi::checkAlienToken() @@ -165,6 +159,10 @@ void CcdbApi::curlInit() void CcdbApi::init(std::string const& host) { + if (host.empty()) { + throw std::invalid_argument("Empty url passed CcdbApi, cannot initialize. Aborting."); + } + // if host is prefixed with "file://" this is a local snapshot // in this case we init the API in snapshot (readonly) mode constexpr const char* SNAPSHOTPREFIX = "file://"; @@ -215,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")); @@ -371,6 +368,10 @@ int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::strin sanitizedEndValidityTimestamp = getFutureTimestamp(60 * 60 * 24 * 1); } if (mInSnapshotMode) { // write local file + if (filename.empty() || buffer == nullptr || size == 0) { + LOGP(alarm, "Snapshot mode does not support headers-only upload"); + return -3; + } auto pthLoc = getSnapshotDir(mSnapshotTopPath, path); o2::utils::createDirectoriesIfAbsent(pthLoc); auto flLoc = getSnapshotFile(mSnapshotTopPath, path, filename); @@ -414,8 +415,14 @@ int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::strin auto mime = curl_mime_init(curl); auto field = curl_mime_addpart(mime); curl_mime_name(field, "send"); - curl_mime_filedata(field, filename.c_str()); - curl_mime_data(field, buffer, size); + if (!filename.empty()) { + curl_mime_filedata(field, filename.c_str()); + } + if (buffer != nullptr && size > 0) { + curl_mime_data(field, buffer, size); + } else { + curl_mime_data(field, "", 0); + } struct curl_slist* headerlist = nullptr; static const char buf[] = "Expect:"; @@ -831,7 +838,7 @@ TObject* CcdbApi::retrieveFromTFile(std::string const& path, std::map const& metadata, - long timestamp, bool preservePath, std::string const& localFileName, std::string const& createdNotAfter, std::string const& createdNotBefore) const + long timestamp, bool preservePath, std::string const& localFileName, std::string const& createdNotAfter, std::string const& createdNotBefore, std::map* outHeaders) const { // we setup the target path for this blob @@ -879,6 +886,9 @@ bool CcdbApi::retrieveBlob(std::string const& path, std::string const& targetdir CCDBQuery querysummary(path, metadata, timestamp); updateMetaInformationInLocalFile(targetpath.c_str(), &headers, &querysummary); + if (outHeaders) { + *outHeaders = std::move(headers); + } return true; } diff --git a/CCDB/src/UploadTool.cxx b/CCDB/src/UploadTool.cxx index 44b8d8e20bc7d..9aba417b4f4a9 100644 --- a/CCDB/src/UploadTool.cxx +++ b/CCDB/src/UploadTool.cxx @@ -147,33 +147,44 @@ int main(int argc, char* argv[]) meta[p.first] = p.second; } - TFile f(filename.c_str()); - auto key = f.GetKey(keyname.c_str()); - if (key) { - // get type of key - auto classname = key->GetClassName(); - auto tcl = TClass::GetClass(classname); - auto object = f.Get(keyname.c_str()); - if (tcl->InheritsFrom("TTree")) { - auto tree = static_cast(object); - tree->LoadBaskets(0x1L << 32); // make tree memory based - tree->SetDirectory(nullptr); - } - // convert classname to typeinfo - // typeinfo - auto ti = tcl->GetTypeInfo(); - - std::cout << " Uploading an object of type " << key->GetClassName() - << " to path " << path << " with timestamp validity from " << starttimestamp - << " to " << endtimestamp << "\n"; - - api.storeAsTFile_impl(object, *ti, path, meta, starttimestamp, endtimestamp); + if (filename == "headersOnly") { + auto ent = meta.find("Redirect"); + std::cout << " Uploading a headers-only object to path " << path << " with timestamp validity from " << starttimestamp << " to " << endtimestamp + << " Redirection to: " << ((ent != meta.end()) ? ent->second : std::string{"none"}) << "\n"; + api.storeAsBinaryFile(nullptr, 0, "ignored", "", path, meta, starttimestamp, endtimestamp); if (!api.isSnapshotMode() && meta.find("adjustableEOV") != meta.end() && meta.find("default") == meta.end()) { - o2::ccdb::CcdbObjectInfo oi(path, classname, filename, meta, starttimestamp, endtimestamp); + o2::ccdb::CcdbObjectInfo oi(path, "", "", meta, starttimestamp, endtimestamp); o2::ccdb::adjustOverriddenEOV(api, oi); } } else { - std::cerr << "Key " << keyname << " does not exist\n"; + TFile f(filename.c_str()); + auto key = f.GetKey(keyname.c_str()); + if (key) { + // get type of key + auto classname = key->GetClassName(); + auto tcl = TClass::GetClass(classname); + auto object = f.Get(keyname.c_str()); + if (tcl->InheritsFrom("TTree")) { + auto tree = static_cast(object); + tree->LoadBaskets(0x1L << 32); // make tree memory based + tree->SetDirectory(nullptr); + } + // convert classname to typeinfo + // typeinfo + auto ti = tcl->GetTypeInfo(); + + std::cout << " Uploading an object of type " << key->GetClassName() + << " to path " << path << " with timestamp validity from " << starttimestamp + << " to " << endtimestamp << "\n"; + + api.storeAsTFile_impl(object, *ti, path, meta, starttimestamp, endtimestamp); + if (!api.isSnapshotMode() && meta.find("adjustableEOV") != meta.end() && meta.find("default") == meta.end()) { + o2::ccdb::CcdbObjectInfo oi(path, classname, filename, meta, starttimestamp, endtimestamp); + o2::ccdb::adjustOverriddenEOV(api, oi); + } + } else { + std::cerr << "Key " << keyname << " does not exist\n"; + } } return 0; diff --git a/CCDB/test/testCcdbApi.cxx b/CCDB/test/testCcdbApi.cxx index 0ba037710cf62..1b6a5d6f0967a 100644 --- a/CCDB/test/testCcdbApi.cxx +++ b/CCDB/test/testCcdbApi.cxx @@ -589,4 +589,11 @@ BOOST_AUTO_TEST_CASE(vectored) for (auto context : contexts) { BOOST_CHECK(context.dest.size() != 0); } -} \ No newline at end of file +} + +BOOST_AUTO_TEST_CASE(empty_url) +{ + CcdbApi api; + string url = ""; + BOOST_CHECK_EXCEPTION(api.init(url), invalid_argument, [](std::invalid_argument const&) -> bool { return true; }); +} diff --git a/CCDB/test/testCcdbApiHeaders.cxx b/CCDB/test/testCcdbApiHeaders.cxx new file mode 100644 index 0000000000000..bcfa2a5b44bc2 --- /dev/null +++ b/CCDB/test/testCcdbApiHeaders.cxx @@ -0,0 +1,413 @@ +// Copyright 2019-2025 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 testCcdbApiHeaders.cxx +/// \brief Test BasicCCDBManager header/metadata information functionality with caching +/// \author martin.oines.eide@cern.ch + +#define BOOST_TEST_MODULE CCDB +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include "CCDB/BasicCCDBManager.h" +#include "CCDB/CCDBTimeStampUtils.h" +#include "CCDB/CcdbApi.h" +#include + +static std::string basePath; +// std::string ccdbUrl = "http://localhost:8080"; +std::string ccdbUrl = "http://ccdb-test.cern.ch:8080"; +bool hostReachable = false; + +/** + * Global fixture, ie general setup and teardown + * Copied from testBasicCCDBManager.cxx + */ +struct Fixture { + Fixture() + { + auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance(); + if (std::getenv("ALICEO2_CCDB_HOST")) { + ccdbUrl = std::string(std::getenv("ALICEO2_CCDB_HOST")); + } + ccdbManager.setURL(ccdbUrl); + hostReachable = ccdbManager.getCCDBAccessor().isHostReachable(); + char hostname[_POSIX_HOST_NAME_MAX]; + gethostname(hostname, _POSIX_HOST_NAME_MAX); + basePath = std::string("Users/m/meide/Tests/") + hostname + "/pid-" + getpid() + "/BasicCCDBManager/"; + + LOG(info) << "Path we will use in this test suite : " + basePath << std::endl; + LOG(info) << "ccdb url: " << ccdbUrl << std::endl; + LOG(info) << "Is host reachable ? --> " << hostReachable << std::endl; + } + ~Fixture() + { + if (hostReachable) { + o2::ccdb::BasicCCDBManager::instance().getCCDBAccessor().truncate(basePath + "*"); // This deletes the data after test is run, disable if you want to inspect the data + LOG(info) << "Test data truncated/deleted (" << basePath << ")" << std::endl; + } + } +}; +BOOST_GLOBAL_FIXTURE(Fixture); +/** + * Just an accessor to the hostReachable variable to be used to determine whether tests can be ran or not. + * Copied from testCcdbApi.cxx + */ +struct if_reachable { + boost::test_tools::assertion_result operator()(boost::unit_test::test_unit_id) + { + return hostReachable; + } +}; + +// Only compare known and stable keys (avoid volatile ones like Date) +static const std::set sStableKeys = { + "ETag", + "Valid-From", + "Valid-Until", + "Created", + "Last-Modified", + "Content-Disposition", + "Content-Location", + "path", + "partName", + "Content-MD5", + "Hello" // TODO find other headers to compare to +}; + +// Test that we get back the same header header keys as we put in (for stable keys) + +BOOST_AUTO_TEST_CASE(testCachedHeaders, *boost::unit_test::precondition(if_reachable())) +{ + /// ━━━━━━━ ARRANGE ━━━━━━━━━ + // First store objects to test with + auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance(); + std::string pathA = basePath + "CachingA"; + std::string pathB = basePath + "CachingB"; + std::string pathC = basePath + "CachingC"; + std::string ccdbObjO = "testObjectO"; + std::string ccdbObjN = "testObjectN"; + std::string ccdbObjX = "testObjectX"; + std::map md = { + {"Hello", "World"}, + {"Key1", "Value1"}, + {"Key2", "Value2"}, + }; + long start = 1000, stop = 3000; + ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjO, pathA, md, start, stop); + ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjN, pathB, md, start, stop); + ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjX, pathC, md, start, stop); + // initilize the BasicCCDBManager + ccdbManager.clearCache(); + ccdbManager.setCaching(true); // This is what we want to test. + + /// ━━━━━━━━━━━ ACT ━━━━━━━━━━━━ + // Plan: get one object, then another, then the first again and check the headers are the same + std::map headers1, headers2, headers3; + + auto* obj1 = ccdbManager.getForTimeStamp(pathA, (start + stop) / 2, &headers1); + auto* obj2 = ccdbManager.getForTimeStamp(pathB, (start + stop) / 2, &headers2); + auto* obj3 = ccdbManager.getForTimeStamp(pathA, (start + stop) / 2, &headers3); // Should lead to a cache hit! + + /// ━━━━━━━━━━━ ASSERT ━━━━━━━━━━━━ + /// Check that we got something + BOOST_REQUIRE(obj1 != nullptr); + BOOST_REQUIRE(obj2 != nullptr); + BOOST_REQUIRE(obj3 != nullptr); + + LOG(debug) << "obj1: " << *obj1; + LOG(debug) << "obj2: " << *obj2; + LOG(debug) << "obj3: " << *obj3; + + // Sanity check + /// Check that the objects are correct + BOOST_TEST(*obj1 == ccdbObjO); + BOOST_TEST(*obj3 == ccdbObjO); + BOOST_TEST(obj3 == obj1); // should be the same object in memory since it is cached + + BOOST_TEST(obj2 != obj1); + + (*obj1) = "ModifiedObject"; + BOOST_TEST(*obj1 == "ModifiedObject"); + BOOST_TEST(*obj3 == "ModifiedObject"); // obj3 and obj1 are the same object in memory + + // Check that the headers are the same for the two retrievals of the same object + BOOST_REQUIRE(headers1.size() != 0); + BOOST_REQUIRE(headers3.size() != 0); + + LOG(debug) << "Headers1 size: " << headers1.size(); + for (const auto& h : headers1) { + LOG(debug) << " " << h.first << " -> " << h.second; + } + LOG(debug) << "Headers3 size: " << headers3.size(); + for (const auto& h : headers3) { + LOG(debug) << " " << h.first << " -> " << h.second; + } + + for (const auto& stableKey : sStableKeys) { + LOG(info) << "Checking key: " << stableKey; + + BOOST_REQUIRE(headers1.count(stableKey) > 0); + BOOST_REQUIRE(headers3.count(stableKey) > 0); + BOOST_TEST(headers1.at(stableKey) == headers3.at(stableKey)); + } + BOOST_TEST(headers1 != headers2, "The headers for different objects should be different"); + + // Test that we can change the map and the two headers are not affected + headers1["NewKey"] = "NewValue"; + headers3["NewKey"] = "DifferentValue"; + BOOST_TEST(headers1["NewKey"] != headers3["NewKey"]); // This tests that we have a deep copy of the headers +} + +BOOST_AUTO_TEST_CASE(testNonCachedHeaders, *boost::unit_test::precondition(if_reachable())) +{ + /// ━━━━━━━ ARRANGE ━━━━━━━━━ + // First store objects to test with + auto& ccdbManager = o2::ccdb::BasicCCDBManager::instance(); + std::string pathA = basePath + "NonCachingA"; + std::string pathB = basePath + "NonCachingB"; + std::string ccdbObjO = "testObjectO"; + std::string ccdbObjN = "testObjectN"; + std::map md = { + {"Hello", "World"}, + {"Key1", "Value1"}, + {"Key2", "Value2"}, + }; + long start = 1000, stop = 2000; + ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjO, pathA, md, start, stop); + ccdbManager.getCCDBAccessor().storeAsTFileAny(&ccdbObjN, pathB, md, start, stop); + // initilize the BasicCCDBManager + ccdbManager.clearCache(); + ccdbManager.setCaching(false); // This is what we want to test, no caching + + /// ━━━━━━━━━━━ ACT ━━━━━━━━━━━━ + // Plan: get one object, then another, then the first again. Then check that the contents is the same but not the object in memory + std::map headers1, headers2, headers3; + + auto* obj1 = ccdbManager.getForTimeStamp(pathA, (start + stop) / 2, &headers1); + auto* obj2 = ccdbManager.getForTimeStamp(pathB, (start + stop) / 2, &headers2); + auto* obj3 = ccdbManager.getForTimeStamp(pathA, (start + stop) / 2, &headers3); // Should not be cached since explicitly disabled + + ccdbManager.setCaching(true); // Restore default state + /// ━━━━━━━━━━━ ASSERT ━━━━━━━━━━━ + /// Check that we got something + BOOST_REQUIRE(obj1 != nullptr); + BOOST_REQUIRE(obj2 != nullptr); + BOOST_REQUIRE(obj3 != nullptr); + + LOG(debug) << "obj1: " << *obj1; + LOG(debug) << "obj2: " << *obj2; + LOG(debug) << "obj3: " << *obj3; + + // Sanity check + /// Check that the objects are correct + BOOST_TEST(*obj1 == ccdbObjO); + BOOST_TEST(*obj3 == ccdbObjO); + BOOST_TEST(obj2 != obj1); + BOOST_TEST(obj3 != obj1); // should NOT be the same object in memory + (*obj1) = "ModifiedObject"; + BOOST_TEST(*obj1 == "ModifiedObject"); + BOOST_TEST(*obj3 != "ModifiedObject"); // obj3 and obj1 are NOT the same object in memory + + BOOST_TEST(headers1.size() == headers3.size()); + + // Remove the date header since it may be different even for the same object since we might have asked in different seconds + headers1.erase("Date"); + headers3.erase("Date"); + BOOST_TEST(headers1 == headers3, "The headers for the same object should be the same even if not cached"); + + BOOST_TEST(headers1 != headers2, "The headers for different objects should be different"); + BOOST_TEST(headers1.size() != 0); + BOOST_TEST(headers3.size() != 0); + BOOST_TEST(headers2.size() != 0); + BOOST_TEST(headers1 != headers2, "The headers for different objects should be different"); + + // cleanup + delete obj1; + delete obj2; + delete obj3; +} + +BOOST_AUTO_TEST_CASE(CacheFirstRetrievalAndHeadersPersistence, *boost::unit_test::precondition(if_reachable())) +{ + /// ━━━━━━━ ARRANGE ━━━━━━━━━ + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + // Prepare two validity slots for same path to test ETag change later + std::string path = basePath + "ObjA"; + std::string objV1 = "ObjectVersion1"; + std::string objV2 = "ObjectVersion2"; + std::map meta1{ + {"UserKey1", "UValue1"}, + {"UserKey2", "UValue2"}}; + long v1start = 10'000; + long v1stop = 20'000; + long v2start = v1stop; // contiguous slot + long v2stop = v2start + (v1stop - v1start); + long mid1 = (v1start + v1stop) / 2; + // Store 2 versions + mgr.getCCDBAccessor().storeAsTFileAny(&objV1, path, meta1, v1start, v1stop); + mgr.getCCDBAccessor().storeAsTFileAny(&objV2, path, meta1, v2start, v2stop); + + mgr.clearCache(); + mgr.setCaching(true); + mgr.setFatalWhenNull(true); + mgr.setTimestamp(mid1); + + /// ━━━━━━━ACT━━━━━━━━━ + std::map headers1, headers2, headers4, headers5; + + // 1) First retrieval WITH headers inside 1st slot + auto* p1 = mgr.getForTimeStamp(path, mid1, &headers1); + size_t fetchedSizeAfterFirst = mgr.getFetchedSize(); + // 2) Second retrieval (cache hit) + auto* p2 = mgr.getForTimeStamp(path, mid1, &headers2); + size_t fetchedSizeAfterSecond = mgr.getFetchedSize(); + // 3) Third retrieval (cache hit) WITHOUT passing headers + auto* p3 = mgr.getForTimeStamp(path, mid1); + // 4) Fourth retrieval with headers again -> should still produce same headers + auto* p4 = mgr.getForTimeStamp(path, mid1, &headers4); + // 5) Fifth retrieval with headers again to check persistence + auto* p5 = mgr.getForTimeStamp(path, mid1, &headers5); + + mgr.setFatalWhenNull(false); // restore default + + /// ━━━━━━━ASSERT━━━━━━━━━ + + BOOST_TEST(p1 != nullptr); + BOOST_TEST(*p1 == objV1); + + BOOST_TEST(headers1.count("UserKey1") == 1); + BOOST_TEST(headers1.count("UserKey2") == 1); + BOOST_TEST(headers1["UserKey1"] == "UValue1"); + BOOST_TEST(headers1["UserKey2"] == "UValue2"); + BOOST_TEST(headers1.count("Valid-From") == 1); + BOOST_TEST(headers1.count("Valid-Until") == 1); + BOOST_TEST(headers1.count("ETag") == 1); + + /* Need to manually amend the headers1 to have cache valid until for comparison sake, + * the header is not set in the first request. + * It is only set if the internal cache of CCDB has seen this object before, apperently. + * This will never happen in this test since it was just created and not asked for before. + */ + headers1["Cache-Valid-Until"] = std::to_string(v1stop); + + /* In rare cases the header date might be different, if the second has ticked over between the requests + */ + headers1.erase("Date"); + headers2.erase("Date"); + headers4.erase("Date"); + headers5.erase("Date"); + + BOOST_TEST(p2 == p1); // same pointer for cached scenario + BOOST_TEST(headers2 == headers1); // identical header map + BOOST_TEST(fetchedSizeAfterSecond == fetchedSizeAfterFirst); // no new fetch + + BOOST_TEST(p3 == p1); + + BOOST_TEST(p4 == p1); + BOOST_TEST(headers4 == headers1); + + // Mutate the returned header map locally and ensure it does not corrupt internal cache + headers4["UserKey1"] = "Tampered"; + BOOST_TEST(p5 == p1); + BOOST_TEST(headers5["UserKey1"] == "UValue1"); // internal unchanged +} + +BOOST_AUTO_TEST_CASE(FailedFetchDoesNotGiveMetadata, *boost::unit_test::precondition(if_reachable())) +{ + + /// ━━━━━━━ ARRANGE ━━━━━━━━━ + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + std::string path = basePath + "FailThenRecover"; + std::string content = "ContentX"; + std::map meta{{"Alpha", "Beta"}}; + long s = 300'000, e = 310'000; + mgr.getCCDBAccessor().storeAsTFileAny(&content, path, meta, s, e); + mgr.clearCache(); + mgr.setCaching(true); + mgr.setFatalWhenNull(false); + + /// ━━━━━━━ ACT ━━━━━━━━━ + // Intentionally pick a timestamp outside validity to fail first + long badTS = s - 1000; + long goodTS = (s + e) / 2; + std::map hFail, hGood; + auto* badObj = mgr.getForTimeStamp(path, badTS, &hFail); + auto* goodObj = mgr.getForTimeStamp(path, goodTS, &hGood); + + /// ━━━━━━━ ASSERT ━━━━━━━━━ + BOOST_TEST(!hFail.empty()); // Should have some headers + BOOST_TEST(hFail["Alpha"] != "Beta"); // But not the metadata + BOOST_TEST(hGood.count("Alpha") == 1); + BOOST_TEST(hGood["Alpha"] == "Beta"); + + mgr.setFatalWhenNull(true); +} + +BOOST_AUTO_TEST_CASE(FirstCallWithoutHeadersThenWithHeaders, *boost::unit_test::precondition(if_reachable())) +{ + + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + std::string path = basePath + "LateHeaders"; + std::string body = "Late"; + std::map meta{{"LateKey", "LateVal"}}; + long s = 400'000, e = 410'000; + mgr.getCCDBAccessor().storeAsTFileAny(&body, path, meta, s, e); + + mgr.clearCache(); + mgr.setCaching(true); + long ts = (s + e) / 2; + + // 1) First call with nullptr headers + auto* first = mgr.getForTimeStamp(path, ts); + BOOST_TEST(first != nullptr); + BOOST_TEST(*first == body); + + // 2) Second call asking for headers - should return the full set + std::map h2; + auto* second = mgr.getForTimeStamp(path, ts, &h2); + BOOST_TEST(second == first); + BOOST_TEST(h2.count("LateKey") == 1); + BOOST_TEST(h2["LateKey"] == "LateVal"); + BOOST_TEST(h2.count("Valid-From") == 1); + BOOST_TEST(h2.count("Valid-Until") == 1); +} + +BOOST_AUTO_TEST_CASE(HeadersAreStableAcrossMultipleHits, *boost::unit_test::precondition(if_reachable())) +{ + + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + std::string path = basePath + "StableHeaders"; + std::string body = "Stable"; + std::map meta{{"HK", "HV"}}; + long s = 500'000, e = 510'000; + mgr.getCCDBAccessor().storeAsTFileAny(&body, path, meta, s, e); + + mgr.clearCache(); + mgr.setCaching(true); + long ts = (s + e) / 2; + + std::map h1; + auto* o1 = mgr.getForTimeStamp(path, ts, &h1); + BOOST_TEST(o1 != nullptr); + BOOST_TEST(h1.count("HK") == 1); + + std::string etag = h1["ETag"]; + for (int i = 0; i < 15; ++i) { + std::map hi; + auto* oi = mgr.getForTimeStamp(path, ts, &hi); + BOOST_TEST(oi == o1); + BOOST_TEST(hi.count("HK") == 1); + BOOST_TEST(hi["ETag"] == etag); + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000000..9d8753a362a56 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +This is the main repository of the ALICE Experiment Simulation, Reconstruction and Analysis Framework. + +The skills specific to developing code for this repository can be found in +the `.skills/` folder of this project. + diff --git a/CODEOWNERS b/CODEOWNERS index 117ff0d92b272..f54738e2ce4e3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -34,7 +34,7 @@ /DataFormats/Detectors/GlobalTracking @shahor02 /DataFormats/Detectors/GlobalTrackingWorkflow @shahor02 /DataFormats/Detectors/HMPID @gvolpe79 -/DataFormats/Detectors/ITSMFT @fprino @mcoquet642 @mconcas @shahor02 +/DataFormats/Detectors/ITSMFT @fprino @mcoquet642 @shahor02 /DataFormats/Detectors/MUON @AliceO2Group/muon-experts @shahor02 /DataFormats/Detectors/PHOS @peressounko @kharlov /DataFormats/Detectors/Passive @sawenzel @@ -43,7 +43,7 @@ /DataFormats/Detectors/TRD @f3sch @bazinski @wille10 /DataFormats/Detectors/Upgrades @mconcas /DataFormats/Detectors/Upgrades/ITS3 @fgrosa @arossi81 -/DataFormats/Detectors/ZDC @coppedis +/DataFormats/Detectors/ZDC @coppedis @cortesep #/DataFormats/Headers #/DataFormats/Legacy @@ -73,9 +73,9 @@ /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 +/Detectors/ZDC @coppedis @cortesep /Detectors/CTF @shahor02 /Detectors/Raw @shahor02 /Detectors/StrangenessTracking @mconcas @mpuccio @fmazzasc diff --git a/Common/Constants/include/CommonConstants/PhysicsConstants.h b/Common/Constants/include/CommonConstants/PhysicsConstants.h index f0198f7a7f61d..e95b4343a63d4 100644 --- a/Common/Constants/include/CommonConstants/PhysicsConstants.h +++ b/Common/Constants/include/CommonConstants/PhysicsConstants.h @@ -31,6 +31,9 @@ namespace o2::constants::physics /// \note Follow kCamelCase naming convention /// \link https://root.cern/doc/master/TPDGCode_8h.html enum Pdg { + kEta = 221, + kOmega = 223, + kEtaPrime = 331, kB0 = 511, kB0Bar = -511, kBPlus = 521, @@ -89,10 +92,15 @@ 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 +constexpr double MassEta = 0.547862; +constexpr double MassOmega = 0.78266; +constexpr double MassEtaPrime = 0.95778; constexpr double MassB0 = 5.27966; constexpr double MassB0Bar = 5.27966; constexpr double MassBPlus = 5.27934; @@ -152,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 ad24d66e2c3a5..ccea1863771f3 100755 --- a/Common/Constants/include/CommonConstants/make_pdg_header.py +++ b/Common/Constants/include/CommonConstants/make_pdg_header.py @@ -12,7 +12,7 @@ # or submit itself to any jurisdiction. """! -@brief Generates the body of a C++ header with PDG codes and particle masses. +@brief Generates and updates the body of a C++ header with PDG codes and particle masses. @author Vít Kučera , Inha University @date 2023-09-21 """ @@ -21,9 +21,12 @@ from ctypes import c_bool from enum import Enum -import ROOT # pylint: disable=import-error +try: + import ROOT # pylint: disable=import-error + from ROOT import o2 +except (ModuleNotFoundError, ImportError) as exc: + raise OSError("O2 environment is not loaded.") from exc -name_script = os.path.basename(__file__) # Enum of PDG_t particles class PdgROOT(Enum): @@ -86,6 +89,9 @@ class PdgROOT(Enum): # Enum of additional particles class Pdg(Enum): + kEta = 221 + kOmega = 223 + kEtaPrime = 331 kB0 = 511 kB0Bar = -511 kBPlus = 521 @@ -145,8 +151,11 @@ class Pdg(Enum): kHyperHelium5 = 1010020050 kHyperHelium4Sigma = 1110020040 kLambda1520_Py = 102134 # PYTHIA code different from PDG + kK1_1270_0 = 10313 + kK1_1270Plus = 10323 -dbPdg = ROOT.o2.O2DatabasePDG + +dbPdg = o2.O2DatabasePDG def mass(code): @@ -156,49 +165,109 @@ def mass(code): return dbPdg.Mass(code, success) -def declare_mass(pdg, type="double") -> str: +def declare_mass(pdg, mass_type="double") -> str: """Returns a C++ declaration of a particle mass constant.""" - return f"constexpr {type} Mass{pdg.name[1:]} = {mass(pdg.value)};\n" + return f"constexpr {mass_type} Mass{pdg.name[1:]} = {mass(pdg.value)};" -# Comment at the beginning of the output -str_block_begin = f"""// BEGINNING OF THE GENERATED BLOCK. -// DO NOT EDIT THIS BLOCK DIRECTLY! -// It has been generated by the {name_script} script. -// For modifications, edit the script and generate this block again. -""" -# Comment at the end of the output -str_block_end = """// END OF THE GENERATED BLOCK -""" -# Start of enum declarations of additional particles -str_enum_head = """/// \\brief Declarations of named PDG codes of particles missing in ROOT PDG_t -/// \\note Follow kCamelCase naming convention -/// \\link https://root.cern/doc/master/TPDGCode_8h.html -enum Pdg { -""" -# End of enum declarations of additional particles -str_enum_foot = "};\n" -# Documentation string for mass declarations of additional particles -str_mass_o2_head = """/// \\brief Declarations of masses for additional particles -""" -# Documentation string for mass declarations of PDG_t particles -str_mass_root_head = """/// \\brief Declarations of masses for particles in ROOT PDG_t -""" +def main(): + """Main function""" + + path_header = "PhysicsConstants.h" + name_script = os.path.basename(__file__) + + # Comment at the beginning of the output + block_begin = "// BEGINNING OF THE GENERATED BLOCK." + # Comment at the end of the output + block_end = "// END OF THE GENERATED BLOCK" + # Preamble with instructions + block_preamble = ( + "// DO NOT EDIT THIS BLOCK DIRECTLY!" + f"\n// It has been generated by the {name_script} script." + "\n// For modifications, edit the script and generate this block again." + ) + # Start of enum declarations of additional particles + enum_o2_head = ( + "/// \\brief Declarations of named PDG codes of particles missing in ROOT PDG_t" + "\n/// \\note Follow kCamelCase naming convention" + "\n/// \\link https://root.cern/doc/master/TPDGCode_8h.html" + "\nenum Pdg {" + ) + # End of enum declarations of additional particles + enum_o2_foot = "};" + # Documentation string for mass declarations of additional particles + mass_o2_head = "/// \\brief Declarations of masses for additional particles" + # Documentation string for mass declarations of PDG_t particles + mass_root_head = "/// \\brief Declarations of masses for particles in ROOT PDG_t" + + # Get header content before and after the generated block. + print(f'File "{path_header}" will be updated.') + try: + with open(path_header, encoding="utf-8") as file: + content_old = file.readlines() + except OSError as exc: + raise OSError(f'Failed to open file "{path_header}".') from exc + lines_header_before: list[str] = [] + lines_header_after: list[str] = [] + got_block_begin = False + got_block_end = False + for line in content_old: + line = line.strip() + if line == block_begin: + got_block_begin = True + if not got_block_begin: + lines_header_before.append(line) + if got_block_end: + lines_header_after.append(line) + if line == block_end: + got_block_end = True + if not got_block_begin: + raise ValueError("Did not find the beginning of the block.") + if not got_block_end: + raise ValueError("Did not find the end of the block.") + + # Additional particles + lines_enum_o2: list[str] = [enum_o2_head] + lines_mass_o2: list[str] = [mass_o2_head] + for pdg_o2 in Pdg: + lines_enum_o2.append(f" {pdg_o2.name} = {pdg_o2.value},") + lines_mass_o2.append(declare_mass(pdg_o2)) + lines_enum_o2[-1] = lines_enum_o2[-1][:-1] # Remove the last comma. + lines_enum_o2.append(enum_o2_foot) + + # PDG_t particles + lines_mass_root: list[str] = [mass_root_head] + for pdg_root in PdgROOT: + lines_mass_root.append(declare_mass(pdg_root)) + + # Header body + content_new = "\n".join( + ( + *lines_header_before, + block_begin, + block_preamble, + "", + *lines_enum_o2, + "", + *lines_mass_o2, + "", + *lines_mass_root, + "", + block_end, + *lines_header_after, + "", + ) + ) + # print(content_new) + + # Overwrite the input file. + try: + with open(path_header, "w", encoding="utf-8") as file: + file.write(content_new) + print(f'File "{path_header}" has been overwritten.') + except OSError as exc: + raise OSError(f'Failed to write to file "{path_header}".') from exc + -# Additional particles -str_enum = str_enum_head -str_mass_o2 = str_mass_o2_head -for c in Pdg: - str_enum += f" {c.name} = {c.value},\n" - str_mass_o2 += declare_mass(c) -str_enum = str_enum[:-2] + "\n" # Remove the last comma. -str_enum += str_enum_foot - -# PDG_t particles -str_mass_root = str_mass_root_head -for d in PdgROOT: - str_mass_root += declare_mass(d) - -# Header body -str_header = "\n".join((str_block_begin, str_enum, str_mass_o2, str_mass_root, str_block_end)) -print(str_header) +if __name__ == "__main__": + main() diff --git a/Common/DCAFitter/CMakeLists.txt b/Common/DCAFitter/CMakeLists.txt index 5c3a93aa7fa74..c0b2d0dca1026 100644 --- a/Common/DCAFitter/CMakeLists.txt +++ b/Common/DCAFitter/CMakeLists.txt @@ -22,8 +22,7 @@ o2_add_library(DCAFitter O2::DetectorsBase) o2_target_root_dictionary(DCAFitter - HEADERS include/DCAFitter/HelixHelper.h - include/DCAFitter/DCAFitterN.h + HEADERS include/DCAFitter/DCAFitterN.h include/DCAFitter/FwdDCAFitterN.h) if (OpenMP_CXX_FOUND) diff --git a/Common/DCAFitter/GPU/cuda/CMakeLists.txt b/Common/DCAFitter/GPU/cuda/CMakeLists.txt index ddc1d09445d7f..6b89207279fe0 100644 --- a/Common/DCAFitter/GPU/cuda/CMakeLists.txt +++ b/Common/DCAFitter/GPU/cuda/CMakeLists.txt @@ -22,14 +22,14 @@ o2_add_library(DCAFitterCUDA set_property(TARGET ${targetName} PROPERTY CUDA_SEPARABLE_COMPILATION ON) # add_compile_options(-lineinfo) -o2_add_test(DCAFitterNCUDA - SOURCES test/testDCAFitterNGPU.cxx - PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats - O2::DCAFitterCUDA - O2::DCAFitter - ROOT::Core - ROOT::Physics - COMPONENT_NAME gpu - LABELS vertexing - ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage - VMCWORKDIR=${CMAKE_BINARY_DIR}/stage/${CMAKE_INSTALL_DATADIR}) \ No newline at end of file +#o2_add_test(DCAFitterNCUDA +# SOURCES test/testDCAFitterNGPU.cxx +# PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats +# O2::DCAFitterCUDA +# O2::DCAFitter +# ROOT::Core +# ROOT::Physics +# COMPONENT_NAME gpu +# LABELS vertexing +# ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage +# VMCWORKDIR=${CMAKE_BINARY_DIR}/stage/${CMAKE_INSTALL_DATADIR}) diff --git a/Common/DCAFitter/GPU/hip/CMakeLists.txt b/Common/DCAFitter/GPU/hip/CMakeLists.txt index f62759bb6ea2c..5e7821a0b8946 100644 --- a/Common/DCAFitter/GPU/hip/CMakeLists.txt +++ b/Common/DCAFitter/GPU/hip/CMakeLists.txt @@ -21,15 +21,15 @@ o2_add_hipified_library(DCAFitterHIP PRIVATE_LINK_LIBRARIES O2::GPUTrackingHIPExternalProvider TARGETVARNAME targetNAme) -o2_add_test(DCAFitterNHIP - SOURCES ../cuda/test/testDCAFitterNGPU.cxx - PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats - O2::DCAFitterHIP - O2::DCAFitter - ROOT::Core - ROOT::Physics - HIPIFIED test - COMPONENT_NAME gpu - LABELS vertexing - ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage - VMCWORKDIR=${CMAKE_BINARY_DIR}/stage/${CMAKE_INSTALL_DATADIR}) \ No newline at end of file +#o2_add_test(DCAFitterNHIP +# SOURCES ../cuda/test/testDCAFitterNGPU.cxx +# PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats +# O2::DCAFitterHIP +# O2::DCAFitter +# ROOT::Core +# ROOT::Physics +# HIPIFIED test +# COMPONENT_NAME gpu +# LABELS vertexing +# ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage +# VMCWORKDIR=${CMAKE_BINARY_DIR}/stage/${CMAKE_INSTALL_DATADIR}) diff --git a/Common/DCAFitter/include/DCAFitter/DCAFitterN.h b/Common/DCAFitter/include/DCAFitter/DCAFitterN.h index df732bd4bde63..2641dec84aed9 100644 --- a/Common/DCAFitter/include/DCAFitter/DCAFitterN.h +++ b/Common/DCAFitter/include/DCAFitter/DCAFitterN.h @@ -17,7 +17,7 @@ #ifndef _ALICEO2_DCA_FITTERN_ #define _ALICEO2_DCA_FITTERN_ -#include "DCAFitter/HelixHelper.h" +#include "ReconstructionDataFormats/HelixHelper.h" #include "DetectorsBase/Propagator.h" #include "MathUtils/Cartesian.h" #include "ReconstructionDataFormats/Track.h" @@ -818,6 +818,7 @@ GPUd() o2::math_utils::SMatrix #include "DetectorsBase/Propagator.h" #include "DetectorsBase/GeometryManager.h" diff --git a/Common/DCAFitter/src/DCAFitterLinkDef.h b/Common/DCAFitter/src/DCAFitterLinkDef.h index 3589ffe559e96..6883369c1b9b6 100644 --- a/Common/DCAFitter/src/DCAFitterLinkDef.h +++ b/Common/DCAFitter/src/DCAFitterLinkDef.h @@ -18,9 +18,6 @@ #pragma link C++ class o2::vertexing::DCAFitterN < 2, o2::track::TrackParCov> + ; #pragma link C++ class o2::vertexing::DCAFitterN < 3, o2::track::TrackParCov> + ; -#pragma link C++ class o2::track::TrackAuxPar + ; -#pragma link C++ class o2::track::CrossInfo + ; - #pragma link C++ function o2::vertexing::DCAFitter2::process(const o2::track::TrackParCov&, const o2::track::TrackParCov&); #pragma link C++ function o2::vertexing::DCAFitter3::process(const o2::track::TrackParCov&, const o2::track::TrackParCov&, const o2::track::TrackParCov&); diff --git a/Common/Field/include/Field/MagFieldFast.h b/Common/Field/include/Field/MagFieldFast.h index acff8f528ad06..ae6431a477923 100644 --- a/Common/Field/include/Field/MagFieldFast.h +++ b/Common/Field/include/Field/MagFieldFast.h @@ -57,7 +57,7 @@ class MagFieldFast bool Field(const math_utils::Point3D xyz, double bxyz[3]) const; bool GetBcomp(EDim comp, const double xyz[3], double& b) const; bool GetBcomp(EDim comp, const float xyz[3], float& b) const; - bool GetBcomp(EDim comp, const math_utils::Point3D xyz, double& b) const; + bool GetBcomp(EDim comp, const math_utils::Point3D xyz, double& b) const; bool GetBcomp(EDim comp, const math_utils::Point3D xyz, float& b) const; bool GetBx(const double xyz[3], double& bx) const { return GetBcomp(kX, xyz, bx); } @@ -66,6 +66,8 @@ class MagFieldFast bool GetBy(const float xyz[3], float& by) const { return GetBcomp(kY, xyz, by); } bool GetBz(const double xyz[3], double& bz) const { return GetBcomp(kZ, xyz, bz); } bool GetBz(const float xyz[3], float& bz) const { return GetBcomp(kZ, xyz, bz); } + bool GetBz(const math_utils::Point3D xyz, double& bz) const { return GetBcomp(kZ, xyz, bz); } + bool GetBz(const math_utils::Point3D xyz, float& bz) const { return GetBcomp(kZ, xyz, bz); } void setFactorSol(float v = 1.f) { mFactorSol = v; } float getFactorSol() const { return mFactorSol; } diff --git a/Common/Field/src/MagFieldFast.cxx b/Common/Field/src/MagFieldFast.cxx index 5caad34d56dd4..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; @@ -145,7 +143,7 @@ bool MagFieldFast::GetBcomp(EDim comp, const double xyz[3], double& b) const } //_______________________________________________________________________ -bool MagFieldFast::GetBcomp(EDim comp, const math_utils::Point3D xyz, double& b) const +bool MagFieldFast::GetBcomp(EDim comp, const math_utils::Point3D xyz, double& b) const { // get field int zSeg, rSeg, quadrant; diff --git a/Common/ML/include/ML/OrtInterface.h b/Common/ML/include/ML/OrtInterface.h index 04a5e0ba5c9fc..987ce8fb4d6dd 100644 --- a/Common/ML/include/ML/OrtInterface.h +++ b/Common/ML/include/ML/OrtInterface.h @@ -51,6 +51,7 @@ class OrtModel void initOptions(std::unordered_map optionsMap); void initEnvironment(); void initSession(); + void initSessionFromBuffer(const char* buffer, size_t bufferSize); void memoryOnDevice(int32_t = 0); bool isInitialized() { return mInitialized; } void resetSession(); diff --git a/Common/ML/src/OrtInterface.cxx b/Common/ML/src/OrtInterface.cxx index 58d80eb9c0bf0..8f88ab18dacbd 100644 --- a/Common/ML/src/OrtInterface.cxx +++ b/Common/ML/src/OrtInterface.cxx @@ -54,7 +54,7 @@ void OrtModel::initOptions(std::unordered_map optionsM // Load from options map if (!optionsMap.contains("model-path")) { - LOG(fatal) << "(ORT) Model path cannot be empty!"; + LOG(fatal) << "(ORT) Model path must be contained in options map!"; } if (!optionsMap["model-path"].empty()) { @@ -138,6 +138,24 @@ void OrtModel::initEnvironment() (mPImplOrt->env)->DisableTelemetryEvents(); // Disable telemetry events } +void OrtModel::initSessionFromBuffer(const char* buffer, size_t bufferSize) +{ + mPImplOrt->sessionOptions.AddConfigEntry("session.load_model_format", "ONNX"); + mPImplOrt->sessionOptions.AddConfigEntry("session.use_ort_model_bytes_directly", "1"); + + mPImplOrt->session = std::make_unique(*mPImplOrt->env, + buffer, + bufferSize, + mPImplOrt->sessionOptions); + mPImplOrt->ioBinding = std::make_unique(*mPImplOrt->session); + + setIO(); + + if (mLoggingLevel < 2) { + LOG(info) << "(ORT) Model loaded successfully from buffer! (inputs: " << printShape(mInputShapes, mInputNames) << ", outputs: " << printShape(mOutputShapes, mInputNames) << ")"; + } +} + void OrtModel::initSession() { if (mAllocateDeviceMemory) { diff --git a/Framework/Core/include/Framework/DataProcessingStateManager.h b/Common/MathUtils/include/MathUtils/BetheBlochAleph.h similarity index 50% rename from Framework/Core/include/Framework/DataProcessingStateManager.h rename to Common/MathUtils/include/MathUtils/BetheBlochAleph.h index eaa1c8e4e5501..bd72faffb0503 100644 --- a/Framework/Core/include/Framework/DataProcessingStateManager.h +++ b/Common/MathUtils/include/MathUtils/BetheBlochAleph.h @@ -9,27 +9,27 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef O2_DATAPROCESSINGSTATEMANAGER_H_ -#define O2_DATAPROCESSINGSTATEMANAGER_H_ - -#include -#include -#include - -struct DataProcessingStateManager { - struct StateIndex { - short id = -1; - short index = -1; - }; - struct StateInfo { - std::string name; - int64_t lastUpdate = 0; - int index = -1; - }; - - static constexpr int MAX_STATES = 1024; - std::vector> states = {}; - std::vector infos = {}; -}; +#ifndef AliceO2_COMMON_BETHEBLOCH_H_ +#define AliceO2_COMMON_BETHEBLOCH_H_ + +#include "GPUCommonDef.h" +#include "GPUCommonMath.h" + +namespace o2::common +{ + +template +GPUdi() T BetheBlochAleph(T bg, T kp1, T kp2, T kp3, T kp4, T kp5) +{ + T beta = bg / o2::gpu::GPUCommonMath::Sqrt(static_cast(1.) + bg * bg); + + T aa = o2::gpu::GPUCommonMath::Pow(beta, kp4); + T bb = o2::gpu::GPUCommonMath::Pow(static_cast(1.) / bg, kp5); + bb = o2::gpu::GPUCommonMath::Log(kp3 + bb); + + return (kp2 - aa - bb) * kp1 / aa; +} + +} // namespace o2::common #endif 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/SimConfig/include/SimConfig/G4Params.h b/Common/SimConfig/include/SimConfig/G4Params.h index fd36ae046d520..aa8aa05263c0a 100644 --- a/Common/SimConfig/include/SimConfig/G4Params.h +++ b/Common/SimConfig/include/SimConfig/G4Params.h @@ -33,6 +33,13 @@ enum class EG4Physics { kUSER = 8 /* allows to give own string combination */ }; +// enumerating possible geometry navigation modes +// (understanding that geometry description is always done with TGeo) +enum class EG4Nav { + kTGeo = 0, /* navigate with TGeo */ + kG4 = 1 /* navigate with G4 native geometry */ +}; + // parameters to influence the G4 engine struct G4Params : public o2::conf::ConfigurableParamHelper { EG4Physics physicsmode = EG4Physics::kFTFP_BERT_EMV_optical; // default physics mode with which to configure G4 @@ -40,6 +47,8 @@ struct G4Params : public o2::conf::ConfigurableParamHelper { std::string configMacroFile = ""; // a user provided g4Config.in file (otherwise standard one fill be taken) std::string userPhysicsList = ""; // possibility to directly give physics list as string + EG4Nav navmode = EG4Nav::kTGeo; // geometry navigation mode (default TGeo) + std::string const& getPhysicsConfigString() const; O2ParamDef(G4Params, "G4"); diff --git a/Common/SimConfig/include/SimConfig/SimParams.h b/Common/SimConfig/include/SimConfig/SimParams.h index cf3ee2b01cf2e..b5f975d1b0c6e 100644 --- a/Common/SimConfig/include/SimConfig/SimParams.h +++ b/Common/SimConfig/include/SimConfig/SimParams.h @@ -44,7 +44,11 @@ struct SimCutParams : public o2::conf::ConfigurableParamHelper { struct SimMaterialParams : public o2::conf::ConfigurableParamHelper { // Local density value takes precedence over global density value, i.e. local values overwrite the global value. float globalDensityFactor = 1.f; // global factor that scales all material densities for systematic studies - std::string localDensityFactor; // Expected format: "SimMaterialParams.localDensityFactor=:,:,..." + // String to set densities on module or material level. Expected format: + // "SimMaterialParams.localDensityFactor=:,:,..." + // Example: "SimMaterialParams.localDensityFactor=TPC/Air:1.2,ITS:5." will scale the density of the Air in TPC + // with 1.2 and to 5.0 for all materials in ITS". + std::string localDensityFactor; O2ParamDef(SimMaterialParams, "SimMaterialParams"); }; diff --git a/Common/SimConfig/src/SimConfig.cxx b/Common/SimConfig/src/SimConfig.cxx index 9407a3c556179..15879687872d5 100644 --- a/Common/SimConfig/src/SimConfig.cxx +++ b/Common/SimConfig/src/SimConfig.cxx @@ -98,7 +98,8 @@ void SimConfig::determineActiveModules(std::vector const& inputargs activeModules[i] != "TF3" && activeModules[i] != "RCH" && activeModules[i] != "MI3" && - activeModules[i] != "ECL") { + activeModules[i] != "ECL" && + activeModules[i] != "FD3") { LOGP(fatal, "List of active modules contains {}, which is not a module from the upgrades.", activeModules[i]); } } @@ -112,7 +113,8 @@ void SimConfig::determineActiveModules(std::vector const& inputargs activeModules[i] == "TF3" || activeModules[i] == "RCH" || activeModules[i] == "MI3" || - activeModules[i] == "ECL") { + activeModules[i] == "ECL" || + activeModules[i] == "FD3") { LOGP(fatal, "List of active modules contains {}, which is not a run 3 module", activeModules[i]); } } @@ -130,6 +132,7 @@ void SimConfig::determineActiveModules(std::vector const& inputargs d == DetID::TF3 || d == DetID::RCH || d == DetID::ECL || + d == DetID::FD3 || d == DetID::MI3) { activeModules.emplace_back(DetID::getName(d)); } @@ -149,7 +152,7 @@ void SimConfig::determineActiveModules(std::vector const& inputargs activeModules.emplace_back("SHIL"); for (int d = DetID::First; d <= DetID::Last; ++d) { #ifdef ENABLE_UPGRADES - if (d != DetID::IT3 && d != DetID::TRK && d != DetID::FT3 && d != DetID::FCT && d != DetID::TF3 && d != DetID::RCH && d != DetID::ECL && d != DetID::MI3) { + if (d != DetID::IT3 && d != DetID::TRK && d != DetID::FT3 && d != DetID::FCT && d != DetID::TF3 && d != DetID::RCH && d != DetID::ECL && d != DetID::FD3 && d != DetID::MI3) { activeModules.emplace_back(DetID::getName(d)); } } @@ -197,7 +200,11 @@ bool SimConfig::determineActiveModulesList(const std::string& version, std::vect return false; } modules = map[version]; - LOGP(info, "Running with official detector version '{}'", version); + static std::string last_version{}; // prevent multiple printouts of same message + if (last_version != version) { + LOGP(info, "Running with official detector version '{}'", version); + last_version = version; + } } // check if specified modules are in list if (inputargs.size() != 1 || inputargs[0] != "all") { diff --git a/Common/SimConfig/src/SimConfigLinkDef.h b/Common/SimConfig/src/SimConfigLinkDef.h index 9c27536be5eb8..a1315e24ffedd 100644 --- a/Common/SimConfig/src/SimConfigLinkDef.h +++ b/Common/SimConfig/src/SimConfigLinkDef.h @@ -29,6 +29,7 @@ #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::conf::DigiParams> + ; #pragma link C++ enum o2::conf::EG4Physics; +#pragma link C++ enum o2::conf::EG4Nav; #pragma link C++ enum o2::conf::SimFieldMode; #pragma link C++ struct o2::conf::G4Params + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::conf::G4Params> + ; diff --git a/Common/Utils/CMakeLists.txt b/Common/Utils/CMakeLists.txt index def743d11791c..849a3d70f62e1 100644 --- a/Common/Utils/CMakeLists.txt +++ b/Common/Utils/CMakeLists.txt @@ -51,6 +51,15 @@ o2_target_root_dictionary(CommonUtils include/CommonUtils/IRFrameSelector.h include/CommonUtils/DebugStreamer.h) +# Extra dictionaries only needed if tests are built +if(BUILD_TESTING) + o2_add_library(CommonUtilsTest + SOURCES src/ConfigurableParamTest.cxx + PUBLIC_LINK_LIBRARIES O2::CommonUtils) + o2_target_root_dictionary(CommonUtilsTest + HEADERS include/CommonUtils/ConfigurableParamTest.h) +endif() + o2_add_test(TreeStream COMPONENT_NAME CommonUtils LABELS utils @@ -87,7 +96,16 @@ o2_add_test(EnumFlags SOURCES test/testEnumFlags.cxx PUBLIC_LINK_LIBRARIES O2::CommonUtils) +o2_add_test(ConfigurableParam + COMPONENT_NAME CommonUtils + LABELS utils + SOURCES test/testConfigurableParam.cxx + PUBLIC_LINK_LIBRARIES O2::CommonUtilsTest) + o2_add_executable(treemergertool COMPONENT_NAME CommonUtils SOURCES src/TreeMergerTool.cxx PUBLIC_LINK_LIBRARIES O2::CommonUtils Boost::program_options ROOT::Core) + +add_library(fpu_support OBJECT src/fpu.cxx) +add_library(O2::fpu_support ALIAS fpu_support) diff --git a/Common/Utils/include/CommonUtils/ConfigurableParam.h b/Common/Utils/include/CommonUtils/ConfigurableParam.h index 39b24bbbbd57c..b9234926b7c40 100644 --- a/Common/Utils/include/CommonUtils/ConfigurableParam.h +++ b/Common/Utils/include/CommonUtils/ConfigurableParam.h @@ -187,6 +187,9 @@ class ConfigurableParam // writes a human readable INI file of all parameters static void writeINI(std::string const& filename, std::string const& keyOnly = ""); + // writes a human readable INI or JSON file depending on the extension + static void write(std::string const& filename, std::string const& keyOnly = ""); + // can be used instead of using API on concrete child classes template static T getValueAs(std::string key) diff --git a/Common/Utils/include/CommonUtils/ConfigurableParamTest.h b/Common/Utils/include/CommonUtils/ConfigurableParamTest.h new file mode 100644 index 0000000000000..547bbf9ba8c38 --- /dev/null +++ b/Common/Utils/include/CommonUtils/ConfigurableParamTest.h @@ -0,0 +1,45 @@ +// 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 COMMON_CONFIGURABLE_PARAM_TEST_H_ +#define COMMON_CONFIGURABLE_PARAM_TEST_H_ + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +namespace o2::conf::test +{ +struct TestParam : public o2::conf::ConfigurableParamHelper { + enum TestEnum : uint8_t { + A, + B, + C + }; + + int iValue{42}; + float fValue{3.14}; + double dValue{3.14}; + bool bValue{true}; + unsigned uValue{1}; + long lValue{1}; + unsigned long ulValue{1}; + long long llValue{1}; + unsigned long long ullValue{1}; + std::string sValue = "default"; + int iValueProvenanceTest{0}; + TestEnum eValue = TestEnum::C; + int caValue[3] = {0, 1, 2}; + + O2ParamDef(TestParam, "TestParam"); +}; +} // namespace o2::conf::test + +#endif diff --git a/Common/Utils/include/CommonUtils/EnumFlags.h b/Common/Utils/include/CommonUtils/EnumFlags.h index fcd7d2d9e5e26..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 { @@ -54,9 +57,13 @@ concept EnumFlagHelper = requires { // functions and also check via concepts expected properties of the enum. // 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; + using UMax = uint64_t; // max represetable type + static_assert(std::numeric_limits::digits <= std::numeric_limits::digits, "Underlying type has more digits than max supported digits"); static constexpr bool isScoped() noexcept { @@ -107,7 +114,7 @@ struct FlagsHelper final { static constexpr size_t MaxUnderScan{std::numeric_limits::digits}; // Maximum digits the underlying type has static constexpr size_t MaxScan{MaxUnderScan + MarginScan}; - // Checks if a given 'localation' contains an enum. + // Checks if a given 'location' contains an enum. template static constexpr bool isValid() noexcept { @@ -127,14 +134,14 @@ struct FlagsHelper final { // check if this is an anonymous enum return true; } - return false; -#else +#elif __GNUC__ else if constexpr (tpeek_v[tp + getSpec().size()] != '(' && tpeek_v.find_first_of(getSpec(), tp + getSpec().size()) != std::string_view::npos) { return true; - } else { - return false; } +#else +#error Unsupported compiler #endif + return false; } // Extract which values are present in the enum by checking all values in @@ -144,8 +151,8 @@ struct FlagsHelper final { { constexpr std::array valid{isValid(MinScan + I)>()...}; constexpr auto count{std::count_if(valid.cbegin(), valid.cend(), [](bool v) noexcept { return v; })}; - static_assert(count > 0, "Requiring non-empty enum!"); - static_assert(count <= MaxUnderScan, "Underlying type of enum has less digits than given expected!"); + static_assert(count > 0, "EnumFlag requires at least one enum value. Check that your enum has consecutive values starting from 0."); + static_assert(count <= MaxUnderScan, "Too many enum values for underlying type. Consider using a larger underlying type or fewer enum values."); std::array values{}; for (size_t idx{}, n{}; n < count; ++idx) { if (valid[idx]) { @@ -160,8 +167,17 @@ struct FlagsHelper final { static constexpr auto Max_v{Values.back()}; // Enum last entry static constexpr auto Min_u_v{static_cast(Min_v)}; // Enum first entry as size_t static constexpr auto Max_u_v{static_cast(Max_v)}; // Enum last entry as size_t - static constexpr bool isContinuous() noexcept { return (Max_u_v - Min_u_v + 1) == count(); } // Is the enum continuous - static constexpr auto MaxRep{((1 << (Max_u_v - Min_u_v + 1)) - 1) << Min_u_v}; // largest representable value + static_assert(Max_u_v < std::numeric_limits::digits, "Max Bit is beyond allow range deferred from underlying type"); + static constexpr bool isContinuous() noexcept { return (Max_u_v - Min_u_v + 1) == count(); } // Is the enum continuous + static constexpr UMax makeMaxRep(size_t min, size_t max) + { + const size_t width = max - min + 1; + if (width >= std::numeric_limits::digits) { + return std::numeric_limits::max(); + } + return ((UMax(1) << width) - 1) << min; + } + static constexpr auto MaxRep{makeMaxRep(Min_u_v, Max_u_v)}; // largest representable value template static constexpr std::string_view getName() @@ -172,7 +188,7 @@ struct FlagsHelper final { } if constexpr (tpeek_v[tp + getSpec().size()] == getSpec()) { #if defined __clang__ - if constexpr (tpeek_v[tp + getSpec().size() + 1] == getSpec()) { + if constexpr (tpeek_v[tp + getSpec().size() + 1] == getSpec()) { return {}; } #endif @@ -214,7 +230,7 @@ struct FlagsHelper final { template static constexpr auto getNameValue{getName()}; - template + template static constexpr auto getNames(std::index_sequence /*unused*/) { if constexpr (with_scope) { @@ -247,8 +263,8 @@ struct FlagsHelper final { static constexpr std::optional fromString(std::string_view str) noexcept { - for (std::size_t i{0}; i < count(); ++i) { - if (Names[i] == str || NamesScoped[i] == str) { + for (size_t i{0}; i < count(); ++i) { + if (isIEqual(Names[i], str) || isIEqual(NamesScoped[i], str)) { return Values[i]; } } @@ -267,7 +283,7 @@ struct FlagsHelper final { return toLower(a) == toLower(b); } - // Case-insensitive comparision for string_view. + // Case-insensitive comparison for string_view. static constexpr bool isIEqual(std::string_view s1, std::string_view s2) noexcept { if (s1.size() != s2.size()) { @@ -284,7 +300,7 @@ struct FlagsHelper final { static constexpr std::string_view None{"none"}; static constexpr bool hasNone() noexcept { - // check that enum does not contain memeber named 'none' + // check that enum does not contain member named 'none' for (size_t i{0}; i < count(); ++i) { if (isIEqual(Names[i], None)) { return true; @@ -296,7 +312,7 @@ struct FlagsHelper final { static constexpr std::string_view All{"all"}; static constexpr bool hasAll() noexcept { - // check that enum does not contain memeber named 'all' + // check that enum does not contain member named 'all' for (size_t i{0}; i < count(); ++i) { if (isIEqual(Names[i], All)) { return true; @@ -305,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 @@ -320,11 +338,15 @@ 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 Classs to aggregate and manage enum-based on-off flags. + * \brief Class to aggregate and manage enum-based on-off flags. * - * This class manages flags as bits in the underlying type of an enum, allowing + * This class manages flags as bits in the underlying type of an enum (upto 64 bits), allowing * manipulation via enum member names. It supports operations akin to std::bitset * but is fully constexpr and is ideal for aggregating multiple on-off booleans, * e.g., enabling/disabling algorithm features. @@ -345,7 +367,10 @@ concept EnumFlag = requires { 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}; @@ -370,17 +395,26 @@ class EnumFlags constexpr EnumFlags(const EnumFlags&) = default; // Move constructor. constexpr EnumFlags(EnumFlags&&) = default; - // Constructor to initialize with the underlyiny type. + // Constructor to initialize with the underlying type. constexpr explicit EnumFlags(U u) : mBits(u) {} // 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 @@ -398,14 +432,14 @@ class EnumFlags // Sets flags from a string representation. // This can be either from a number representation (binary or digits) or // a concatenation of the enums members name e.g., 'Enum1|Enum2|...' - void set(const std::string& s = "", int base = 2) + void set(const std::string& s, int base = DefaultBase) { - // on throw restore previous state and rethrow - const U prev = mBits; - reset(); if (s.empty()) { // no-op return; } + // on throw restore previous state and rethrow + const U prev = mBits; + reset(); try { setImpl(s, base); } catch (const std::exception& e) { @@ -413,8 +447,9 @@ class EnumFlags throw; } } +#endif // Returns the raw bitset value. - constexpr auto value() const noexcept + [[nodiscard]] constexpr auto value() const noexcept { return mBits; } @@ -426,32 +461,42 @@ class EnumFlags } // Resets a specific flag. - template - requires std::is_same_v + template T> constexpr void reset(T t) { mBits &= ~to_bit(t); } // Tests if a specific flag is set. - template - requires std::is_same_v + template T> [[nodiscard]] constexpr bool test(T t) const noexcept { return (mBits & to_bit(t)) != None; } + // Tests if all specified flags are set. + template ... Ts> + [[nodiscard]] constexpr bool test(Ts... flags) const noexcept + { + return ((test(flags) && ...)); + } + // Sets a specific flag. - template - requires std::is_same_v + template T> constexpr void set(T t) noexcept { mBits |= to_bit(t); } + // Sets multiple specific flags. + template ... Ts> + constexpr void set(Ts... flags) noexcept + { + (set(flags), ...); + } + // Toggles a specific flag. - template - requires std::is_same_v + template T> constexpr void toggle(T t) noexcept { mBits ^= to_bit(t); @@ -463,6 +508,13 @@ class EnumFlags return mBits != None; } + // Checks if all flags are set. +#ifndef GPUCA_GPUCODE + [[nodiscard]] constexpr bool all() const noexcept + { + return mBits == All; + } + // Returns the bitset as a binary string. [[nodiscard]] std::string string() const { @@ -502,29 +554,29 @@ class EnumFlags } return oss.str(); } +#endif // Checks if any flag is set (Boolean context). - constexpr explicit operator bool() const noexcept + [[nodiscard]] constexpr explicit operator bool() const noexcept { return any(); } // Check if given flag is set. - template - requires std::is_same_v - constexpr bool operator[](const T t) noexcept + template T> + [[nodiscard]] constexpr bool operator[](const T t) const noexcept { return test(t); } // Checks if two flag sets are equal. - constexpr bool operator==(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr bool operator==(const EnumFlags& o) const noexcept { return mBits == o.mBits; } // Checks if two flag sets are not equal. - constexpr bool operator!=(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr bool operator!=(const EnumFlags& o) const noexcept { return mBits != o.mBits; } @@ -536,8 +588,7 @@ class EnumFlags constexpr EnumFlags& operator=(EnumFlags&& o) = default; // Performs a bitwise OR with a flag. - template - requires std::is_same_v + template T> constexpr EnumFlags& operator|=(T t) noexcept { mBits |= to_bit(t); @@ -545,8 +596,7 @@ class EnumFlags } // Performs a bitwise AND with a flag. - template - requires std::is_same_v + template T> constexpr EnumFlags& operator&=(T t) noexcept { mBits &= to_bit(t); @@ -554,8 +604,7 @@ class EnumFlags } // Returns a flag set with a bitwise AND. - template - requires std::is_same_v + template T> constexpr EnumFlags operator&(T t) const noexcept { return EnumFlags(mBits & to_bit(t)); @@ -583,7 +632,13 @@ class EnumFlags // Performs a bitwise XOR with another flag set. constexpr EnumFlags operator^(const EnumFlags& o) const noexcept { - return Flags(mBits ^ o.mBits); + return EnumFlags(mBits ^ o.mBits); + } + + // Performs a bitwise and with another flag set. + constexpr EnumFlags operator&(const EnumFlags& o) const noexcept + { + return EnumFlags(mBits & o.mBits); } // Performs a bitwise XOR assignment. @@ -595,19 +650,20 @@ class EnumFlags // Checks if all specified flags are set. template - constexpr bool all_of(Ts... flags) const noexcept + [[nodiscard]] constexpr bool all_of(Ts... flags) const noexcept { - return ((test(flags) && ...)); + return test(flags...); } // Checks if none of the specified flags are set. template - constexpr bool none_of(Ts... flags) const noexcept + [[nodiscard]] constexpr bool none_of(Ts... flags) const noexcept { return (!(test(flags) || ...)); } // Serializes the flag set to a string. +#ifndef GPUCA_GPUCODE [[nodiscard]] std::string serialize() const { return std::to_string(mBits); @@ -616,68 +672,126 @@ class EnumFlags // Deserializes a string into the flag set. void deserialize(const std::string& data) { - uint64_t v = std::stoul(data); + typename H::UMax v = std::stoul(data); if (v > H::MaxRep) { throw std::out_of_range("Values exceeds enum range."); } mBits = static_cast(v); } +#endif // Counts the number of set bits (active flags). [[nodiscard]] constexpr size_t count() const noexcept { - size_t c{0}; - for (size_t i{H::Min_u_v}; i < H::Max_u_v; ++i) { - if ((mBits & (U(1) << i)) != U(0)) { - ++c; - } - } - return c; + return std::popcount(mBits); } // Returns the union of two flag sets. - constexpr EnumFlags union_with(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr EnumFlags union_with(const EnumFlags& o) const noexcept { return EnumFlags(mBits | o.mBits); } // Returns the intersection of two flag sets. - constexpr EnumFlags intersection_with(const EnumFlags& o) const noexcept + [[nodiscard]] constexpr EnumFlags intersection_with(const EnumFlags& o) const noexcept { return EnumFlags(mBits & o.mBits); } // Checks if all flags in another Flags object are present in the current object. - constexpr bool contains(const EnumFlags& other) const noexcept + [[nodiscard]] constexpr bool contains(const EnumFlags& other) const noexcept { return (mBits & other.mBits) == other.mBits; } private: - // Set implemnetation, bits was zeroed before. + // 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 + auto isValidForBase = [](unsigned char c, int base) -> bool { + if (base == 2) { + return c == '0' || c == '1'; + } + if (base == 10) { + return std::isdigit(c); + } + if (base == 16) { + return std::isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + return false; + }; + + // hex + if (base == 16) { + std::string_view hex_str{s}; + // Strip optional 0x or 0X prefix + if (s.size() >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + hex_str.remove_prefix(2); + } + if (hex_str.empty()) { + throw std::invalid_argument("Empty hexadecimal string."); + } + if (!std::all_of(hex_str.begin(), hex_str.end(), [&](unsigned char c) { return isValidForBase(c, 16); })) { + throw std::invalid_argument("Invalid hexadecimal string."); + } + typename H::UMax v = std::stoul(std::string(hex_str), nullptr, 16); + if (v > H::MaxRep) { + throw std::out_of_range("Value exceeds enum range."); + } + mBits = static_cast(v); + return; + } + + // decimal and binary if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); })) { - if (base == 2) { // check of only 0 and 1 in string - if (!std::all_of(s.begin(), s.end(), [](char c) { return c == '0' || c == '1'; })) { + if (base == 2) { + // Binary: check only 0 and 1 + if (!std::all_of(s.begin(), s.end(), [&](unsigned char c) { return isValidForBase(c, 2); })) { throw std::invalid_argument("Invalid binary string."); } } - uint64_t v = std::stoul(s, nullptr, base); + typename H::UMax v = std::stoul(std::string(s), nullptr, base); if (v > H::MaxRep) { - throw std::out_of_range("Values exceeds enum range."); + throw std::out_of_range("Value exceeds enum range."); } mBits = static_cast(v); - } else if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalnum(c) != 0 || c == '|' || c == ' ' || c == ':' || c == ','; })) { + } + // enum name strings + else if (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isalnum(c) != 0 || c == '|' || c == ' ' || c == ':' || c == ',' || c == ';'; })) { std::string cs{s}; std::transform(cs.begin(), cs.end(), cs.begin(), [](unsigned char c) { return std::tolower(c); }); + if (cs == H::All) { mBits = All; } else if (cs == H::None) { mBits = None; } else { - char token = (s.find(',') != std::string::npos) ? ',' : '|'; - for (const auto& tok : Str::tokenize(s, token)) { + // Detect delimiter and ensure only one type is used + char token = ' '; + size_t pipePos = s.find('|'); + size_t commaPos = s.find(','); + size_t semiPos = s.find(';'); + + // Count how many different delimiters exist + int delimiterCount = (pipePos != std::string_view::npos ? 1 : 0) + + (commaPos != std::string_view::npos ? 1 : 0) + + (semiPos != std::string_view::npos ? 1 : 0); + + if (delimiterCount > 1) { + throw std::invalid_argument("Mixed delimiters not allowed!"); + } + + if (pipePos != std::string_view::npos) { + token = '|'; + } else if (commaPos != std::string_view::npos) { + token = ','; + } else if (semiPos != std::string_view::npos) { + token = ';'; + } + + for (const auto& tok : Str::tokenize(std::string(s), token)) { if (auto e = H::fromString(tok)) { mBits |= to_bit(*e); } else { @@ -689,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 8a09a903bf32f..8d4c0a2c1c4f8 100644 --- a/Common/Utils/include/CommonUtils/NameConf.h +++ b/Common/Utils/include/CommonUtils/NameConf.h @@ -100,9 +100,15 @@ 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(); + // create name to dump config file + static std::string getConfigOutputFileName(const std::string& procName, const std::string& confName = "", bool json = true); + protected: // helper method to build filenames static std::string buildFileName(const std::string_view prefix, const std::string_view delimiter, const std::string_view defPrefix, const std::string_view defName, diff --git a/Common/Utils/include/CommonUtils/StringUtils.h b/Common/Utils/include/CommonUtils/StringUtils.h index 710632fc7dbfe..cfe29e065a78e 100644 --- a/Common/Utils/include/CommonUtils/StringUtils.h +++ b/Common/Utils/include/CommonUtils/StringUtils.h @@ -136,6 +136,9 @@ struct Str { // return vector of tokens from the string with provided delimiter. If requested, trim the spaces from tokens static std::vector tokenize(const std::string& src, char delim, bool trimToken = true, bool skipEmpty = true); + // return vector of tokens from the string with provided delimiters. If requested, trim the spaces from tokens + static std::vector tokenize(const std::string& src, const std::string& delim, bool trimToken = true, bool skipEmpty = true); + // concatenate arbitrary number of strings template static std::string concat_string(Ts const&... ts) diff --git a/Framework/TestWorkflows/src/o2_sim_its_ALP3.h b/Common/Utils/src/CommonUtilsTestLinkDef.h similarity index 67% rename from Framework/TestWorkflows/src/o2_sim_its_ALP3.h rename to Common/Utils/src/CommonUtilsTestLinkDef.h index f9c465fcf5717..9ee67f62fd7d0 100644 --- a/Framework/TestWorkflows/src/o2_sim_its_ALP3.h +++ b/Common/Utils/src/CommonUtilsTestLinkDef.h @@ -9,17 +9,13 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef WORKFLOWS_O2_SIM_ITS_ALP3 -#define WORKFLOWS_O2_SIM_ITS_ALP3 +#ifdef __CLING__ -#include "Framework/DataProcessorSpec.h" +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -namespace o2 -{ -namespace workflows -{ -o2::framework::DataProcessorSpec sim_its_ALP3(); -} -} // namespace o2 +#pragma link C++ class o2::conf::test::TestParam + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::conf::test::TestParam> + ; -#endif // WORKFLOWS_O2_SIM_ITS_ALP3 +#endif diff --git a/Common/Utils/src/ConfigurableParam.cxx b/Common/Utils/src/ConfigurableParam.cxx index 8e242952bd61d..fd69f51402cd5 100644 --- a/Common/Utils/src/ConfigurableParam.cxx +++ b/Common/Utils/src/ConfigurableParam.cxx @@ -77,6 +77,30 @@ bool keyInTree(boost::property_tree::ptree* pt, const std::string& key) return reply; } +// Convert a type info to the appropiate literal suffix +std::string getLiteralSuffixFromType(const std::type_info& type) +{ + if (type == typeid(float)) { + return "f"; + } + if (type == typeid(long double)) { + return "l"; + } + if (type == typeid(unsigned int)) { + return "u"; + } + if (type == typeid(unsigned long)) { + return "ul"; + } + if (type == typeid(long long)) { + return "ll"; + } + if (type == typeid(unsigned long long)) { + return "ull"; + } + return ""; +} + // ------------------------------------------------------------------ void EnumRegistry::add(const std::string& key, const TDataMember* dm) @@ -168,6 +192,19 @@ int EnumLegalValues::getIntValue(const std::string& value) const // ----------------------------------------------------------------- +void ConfigurableParam::write(std::string const& filename, std::string const& keyOnly) +{ + if (o2::utils::Str::endsWith(filename, ".ini")) { + writeINI(filename, keyOnly); + } else if (o2::utils::Str::endsWith(filename, ".json")) { + writeJSON(filename, keyOnly); + } else { + throw std::invalid_argument(fmt::format("ConfigurabeParam output file name {} extension is neither .json nor .ini", filename)); + } +} + +// ----------------------------------------------------------------- + void ConfigurableParam::writeINI(std::string const& filename, std::string const& keyOnly) { if (sOutputDir == "/dev/null") { @@ -179,7 +216,10 @@ void ConfigurableParam::writeINI(std::string const& filename, std::string const& if (!keyOnly.empty()) { // write ini for selected key only try { boost::property_tree::ptree kTree; - kTree.add_child(keyOnly, sPtree->get_child(keyOnly)); + auto keys = o2::utils::Str::tokenize(keyOnly, " ,;", true, true); + for (const auto& k : keys) { + kTree.add_child(k, sPtree->get_child(k)); + } boost::property_tree::write_ini(outfilename, kTree); } catch (const boost::property_tree::ptree_bad_path& err) { LOG(fatal) << "non-existing key " << keyOnly << " provided to writeINI"; @@ -204,12 +244,42 @@ void ConfigurableParam::setValue(std::string const& key, std::string const& valu initialize(); } assert(sPtree); + auto setValueImpl = [&](std::string const& value) { + sPtree->put(key, value); + auto changed = updateThroughStorageMapWithConversion(key, value); + if (changed != EParamUpdateStatus::Failed) { + sValueProvenanceMap->find(key)->second = kRT; // set to runtime + } + }; try { if (sPtree->get_optional(key).is_initialized()) { - sPtree->put(key, valuestring); - auto changed = updateThroughStorageMapWithConversion(key, valuestring); - if (changed != EParamUpdateStatus::Failed) { - sValueProvenanceMap->find(key)->second = kRT; // set to runtime + try { + // try first setting value without stripping a literal suffix + setValueImpl(valuestring); + } catch (...) { + // try second stripping the expected literal suffix value for fundamental types + auto iter = sKeyToStorageMap->find(key); + if (iter == sKeyToStorageMap->end()) { + std::cerr << "Error in setValue (string) key is not known\n"; + return; + } + const auto expectedSuffix = getLiteralSuffixFromType(iter->second.first); + if (!expectedSuffix.empty()) { + auto valuestringLower = valuestring; + std::transform(valuestring.cbegin(), valuestring.cend(), valuestringLower.begin(), tolower); + if (valuestringLower.ends_with(expectedSuffix)) { + std::string strippedValue = valuestringLower.substr(0, valuestringLower.length() - expectedSuffix.length()); + setValueImpl(strippedValue); + } else { + // check if it has a different suffix and throw + for (const auto& suffix : {"f", "l", "u", "ul", "ll", "ull"}) { + if (valuestringLower.ends_with(suffix) && suffix != expectedSuffix) { + throw std::invalid_argument("Wrong type suffix: expected " + expectedSuffix + " but got " + suffix); + } + } + throw; // just rethrow the original exception + } + } } } } catch (std::exception const& e) { @@ -230,7 +300,10 @@ void ConfigurableParam::writeJSON(std::string const& filename, std::string const if (!keyOnly.empty()) { // write ini for selected key only try { boost::property_tree::ptree kTree; - kTree.add_child(keyOnly, sPtree->get_child(keyOnly)); + auto keys = o2::utils::Str::tokenize(keyOnly, " ,;", true, true); + for (const auto& k : keys) { + kTree.add_child(k, sPtree->get_child(k)); + } boost::property_tree::write_json(outfilename, kTree); } catch (const boost::property_tree::ptree_bad_path& err) { LOG(fatal) << "non-existing key " << keyOnly << " provided to writeJSON"; diff --git a/DataFormats/Detectors/MUON/MCH/src/DsChannelGroup.cxx b/Common/Utils/src/ConfigurableParamTest.cxx similarity index 85% rename from DataFormats/Detectors/MUON/MCH/src/DsChannelGroup.cxx rename to Common/Utils/src/ConfigurableParamTest.cxx index bcf10d74c95ff..5115a8dfe889d 100644 --- a/DataFormats/Detectors/MUON/MCH/src/DsChannelGroup.cxx +++ b/Common/Utils/src/ConfigurableParamTest.cxx @@ -9,8 +9,5 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsMCH/DsChannelGroup.h" - -std::string o2::mch::DsChannelId() const -{ -} +#include "CommonUtils/ConfigurableParamTest.h" +O2ParamImpl(o2::conf::test::TestParam); 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 5a5f644f2da39..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"); @@ -111,3 +116,12 @@ std::string NameConf::getCCDBServer() { return Instance().mCCDBServer; } + +std::string NameConf::getConfigOutputFileName(const std::string& procName, const std::string& confName, bool json) +{ + std::string nm = procName; + if (!confName.empty()) { + nm += '_' + confName; + } + return fmt::format("ConfigParam_{}.{}", nm, json ? "json" : "ini"); +} diff --git a/Common/Utils/src/StringUtils.cxx b/Common/Utils/src/StringUtils.cxx index 687225d069ed2..89f834d9a8f2e 100644 --- a/Common/Utils/src/StringUtils.cxx +++ b/Common/Utils/src/StringUtils.cxx @@ -17,6 +17,7 @@ #include #endif #include +#include using namespace o2::utils; @@ -37,6 +38,35 @@ std::vector Str::tokenize(const std::string& src, char delim, bool return tokens; } +std::vector Str::tokenize(const std::string& src, const std::string& delim, bool trimToken, bool skipEmpty) +{ + std::string inptStr{src}; + char* input = inptStr.data(); + auto mystrtok = [&]() -> char* { + input += std::strspn(input, delim.c_str()); + if (*input == '\0') { + return nullptr; + } + char* const token = input; + input += std::strcspn(input, delim.c_str()); + if (*input != '\0') { + *input++ = '\0'; + } + return token; + }; + std::vector tokens; + while (*input != '\0') { + std::string token = mystrtok(); + if (trimToken) { + trim(token); + } + if (!token.empty() || !skipEmpty) { + tokens.push_back(std::move(token)); + } + } + return tokens; +} + // replace all occurencies of from by to, return count int Str::replaceAll(std::string& s, const std::string& from, const std::string& to) { diff --git a/Common/Utils/test/testConfigurableParam.cxx b/Common/Utils/test/testConfigurableParam.cxx new file mode 100644 index 0000000000000..3ef177aaca3fe --- /dev/null +++ b/Common/Utils/test/testConfigurableParam.cxx @@ -0,0 +1,145 @@ +// 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 BOOST_TEST_MODULE Test ConfigurableParams +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include +#include + +#include "CommonUtils/ConfigurableParamTest.h" + +using namespace o2::conf; +using namespace o2::conf::test; + +BOOST_AUTO_TEST_CASE(ConfigurableParam_Basic) +{ + // Tests the default parameters and also getter helpers. + auto& param = TestParam::Instance(); + BOOST_CHECK_EQUAL(param.iValue, 42); + BOOST_CHECK_EQUAL(param.dValue, 3.14); + BOOST_CHECK_EQUAL(param.bValue, true); + BOOST_CHECK_EQUAL(param.sValue, "default"); + BOOST_CHECK_EQUAL(static_cast(param.eValue), 2); + + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.iValue"), 42); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.dValue"), 3.14); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.bValue"), true); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.sValue"), "default"); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_SG_Fundamental) +{ + // tests runtime setting and getting for fundamental types + ConfigurableParam::setValue("TestParam.iValue", "100"); + ConfigurableParam::setValue("TestParam.dValue", "2.718"); + ConfigurableParam::setValue("TestParam.bValue", "0"); + ConfigurableParam::setValue("TestParam.sValue", "modified"); + ConfigurableParam::setValue("TestParam.eValue", "0"); + + auto& param = TestParam::Instance(); + param.printKeyValues(); + BOOST_CHECK_EQUAL(param.iValue, 100); + BOOST_CHECK_EQUAL(param.dValue, 2.718); + BOOST_CHECK_EQUAL(param.bValue, false); + BOOST_CHECK_EQUAL(param.sValue, "modified"); + BOOST_CHECK_EQUAL(static_cast(param.eValue), 0); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_SG_CArray) +{ + // tests setting and getting for a c-style array type + auto& param = TestParam::Instance(); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.caValue[0]"), 0); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.caValue[1]"), 1); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.caValue[2]"), 2); + + ConfigurableParam::setValue("TestParam.caValue[1]", "99"); + BOOST_CHECK_EQUAL(ConfigurableParam::getValueAs("TestParam.caValue[1]"), 99); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_Provenance) +{ + // tests correct setting of provenance + BOOST_CHECK_EQUAL(ConfigurableParam::getProvenance("TestParam.iValueProvenanceTest"), ConfigurableParam::EParamProvenance::kCODE); + ConfigurableParam::setValue("TestParam.iValueProvenanceTest", "123"); + BOOST_CHECK_EQUAL(ConfigurableParam::getProvenance("TestParam.iValueProvenanceTest"), ConfigurableParam::EParamProvenance::kRT); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_Ini) +{ + // test for ini file serialization + const std::string testFileName = "test_config.ini"; + auto iValueBefore = TestParam::Instance().iValue; + auto sValueBefore = TestParam::Instance().sValue; + ConfigurableParam::writeINI(testFileName); + ConfigurableParam::setValue("TestParam.iValue", "999"); + ConfigurableParam::setValue("TestParam.sValue", testFileName); + ConfigurableParam::updateFromFile(testFileName); + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, iValueBefore); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, sValueBefore); + std::remove(testFileName.c_str()); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_Json) +{ + // test for json file serialization + const std::string testFileName = "test_config.json"; + auto iValueBefore = TestParam::Instance().iValue; + auto sValueBefore = TestParam::Instance().sValue; + ConfigurableParam::writeJSON(testFileName); + ConfigurableParam::setValue("TestParam.iValue", "999"); + ConfigurableParam::setValue("TestParam.sValue", testFileName); + ConfigurableParam::updateFromFile(testFileName); + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, iValueBefore); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, sValueBefore); + std::remove(testFileName.c_str()); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_ROOT) +{ + // test for root file serialization + const std::string testFileName = "test_config.root"; + auto iValueBefore = TestParam::Instance().iValue; + auto sValueBefore = TestParam::Instance().sValue; + TFile* testFile = TFile::Open(testFileName.c_str(), "RECREATE"); + TestParam::Instance().serializeTo(testFile); + testFile->Close(); + ConfigurableParam::setValue("TestParam.iValue", "999"); + ConfigurableParam::setValue("TestParam.sValue", testFileName); + ConfigurableParam::fromCCDB(testFileName); + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, iValueBefore); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, sValueBefore); + std::remove(testFileName.c_str()); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_Cli) +{ + // test setting values from as a cli arg string + ConfigurableParam::updateFromString("TestParam.iValue=55;TestParam.sValue=cli"); + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, 55); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, "cli"); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_LiteralSuffix) +{ + // test setting values with the correct literal suffix + ConfigurableParam::updateFromString("TestParam.fValue=42.f"); + BOOST_CHECK_EQUAL(TestParam::Instance().fValue, 42.f); + + ConfigurableParam::setValue("TestParam.ullValue", "999ull"); + BOOST_CHECK_EQUAL(TestParam::Instance().ullValue, 999ULL); + // check using wrong literal suffix fails, prints error to std + ConfigurableParam::setValue("TestParam.ullValue", "888u"); + BOOST_CHECK_NE(TestParam::Instance().ullValue, 888); +} diff --git a/Common/Utils/test/testEnumFlags.cxx b/Common/Utils/test/testEnumFlags.cxx index 5c8b71eb9040a..9101ffb97fdfe 100644 --- a/Common/Utils/test/testEnumFlags.cxx +++ b/Common/Utils/test/testEnumFlags.cxx @@ -14,6 +14,9 @@ #define BOOST_TEST_DYN_LINK #include +#include +#include + #include #include @@ -21,13 +24,25 @@ // Example enum to use with EnumFlags enum class TestEnum : uint8_t { - Bit1, + Bit1 = 0, Bit2, Bit3, Bit4, Bit5VeryLongName, }; +// Very long enum +// to test that it works beyond 32 bits upto 64 bits +#define ENUM_BIT_NAME(n) Bit##n +#define ENUM_BIT_NAME_EXPAND(n) ENUM_BIT_NAME(n) +#define ENUM_BIT(z, n, _) ENUM_BIT_NAME_EXPAND(BOOST_PP_INC(n)) = (n), +enum class TestEnumLong : uint64_t { + BOOST_PP_REPEAT(64, ENUM_BIT, _) +}; +#undef ENUM_BIT +#undef ENUM_BIT_NAME +#undef ENUM_BIT_NAME_EXPAND + BOOST_AUTO_TEST_CASE(Flags_test) { using EFlags = o2::utils::EnumFlags; @@ -59,11 +74,22 @@ BOOST_AUTO_TEST_CASE(Flags_test) multipleFlags.reset(); BOOST_TEST(!multipleFlags.any()); + // Test multiset + multipleFlags.reset(); + multipleFlags.set(TestEnum::Bit2, TestEnum::Bit4); + BOOST_TEST(!multipleFlags.test(TestEnum::Bit1)); + BOOST_TEST(multipleFlags.test(TestEnum::Bit2)); + BOOST_TEST(!multipleFlags.test(TestEnum::Bit3)); + BOOST_TEST(multipleFlags.test(TestEnum::Bit4)); + BOOST_TEST(!multipleFlags.test(TestEnum::Bit5VeryLongName)); + // Test operator| EFlags combinedFlags = flag1 | EFlags(TestEnum::Bit2); BOOST_TEST(combinedFlags.test(TestEnum::Bit1)); BOOST_TEST(combinedFlags.test(TestEnum::Bit2)); BOOST_TEST(!combinedFlags.test(TestEnum::Bit3)); + combinedFlags |= TestEnum::Bit5VeryLongName; + BOOST_TEST(combinedFlags.test(TestEnum::Bit5VeryLongName)); // Test operator[] BOOST_TEST(combinedFlags[TestEnum::Bit1]); @@ -141,7 +167,7 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_TEST(flags.test(TestEnum::Bit4)); } - { // test with different delimiter + { // test with , delimiter std::string str = "Bit4,TestEnum::Bit2 , Bit1 "; flags.set(str); BOOST_TEST(flags.test(TestEnum::Bit1)); @@ -150,6 +176,15 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_TEST(flags.test(TestEnum::Bit4)); } + { // test with ; delimiter + std::string str = "Bit4;TestEnum::Bit2 ; Bit1 "; + flags.set(str); + BOOST_TEST(flags.test(TestEnum::Bit1)); + BOOST_TEST(flags.test(TestEnum::Bit2)); + BOOST_TEST(!flags.test(TestEnum::Bit3)); + BOOST_TEST(flags.test(TestEnum::Bit4)); + } + { // throw test with mixed delimiter std::string str = "Bit4|TestEnum::Bit2 , Bit1 "; BOOST_CHECK_THROW(flags.set(str), std::invalid_argument); @@ -235,6 +270,14 @@ BOOST_AUTO_TEST_CASE(Flags_test) EFlags flags3{TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3}; EFlags flags4{TestEnum::Bit2, TestEnum::Bit3, TestEnum::Bit4}; + // test xor + auto flagsXOR = flags3 ^ flags4; + BOOST_CHECK(flagsXOR.test(TestEnum::Bit1, TestEnum::Bit4)); + + // test and + auto flagsAND = flags3 & flags4; + BOOST_CHECK(flagsAND.test(TestEnum::Bit2, TestEnum::Bit3)); + // Perform an intersection operation EFlags intersectionFlags = flags3.intersection_with(flags4); BOOST_CHECK(intersectionFlags.test(TestEnum::Bit2)); @@ -244,6 +287,14 @@ BOOST_AUTO_TEST_CASE(Flags_test) BOOST_CHECK_EQUAL(intersectionFlags.value(), 6); // 0110 in binary } + { + // Check special flag names. + EFlags flag("all"); + BOOST_CHECK(flag.all()); + flag.set("none"); + BOOST_CHECK(!flag.any()); + } + { // Create two flag sets EFlags flags1{TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3}; @@ -257,4 +308,346 @@ BOOST_AUTO_TEST_CASE(Flags_test) EFlags flags3{TestEnum::Bit4}; BOOST_CHECK(!flags1.contains(flags3)); // flags1 does not contain flags3 } + + { + // Test compilation using an enum with more than 32 bits + // Also tests space delimiter and construction from string. + o2::utils::EnumFlags test("Bit32 Bit34"); + BOOST_CHECK(test.test(TestEnumLong::Bit32, TestEnumLong::Bit34)); + BOOST_CHECK(!test.test(TestEnumLong::Bit1, TestEnumLong::Bit23)); + } +} + +BOOST_AUTO_TEST_CASE(Flags_case_insensitive_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test case-insensitive flag names + { + EFlags flags("bit1"); // lowercase + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(!flags.test(TestEnum::Bit2)); + } + + { + EFlags flags("BIT2"); // uppercase + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(!flags.test(TestEnum::Bit1)); + } + + { + EFlags flags("BiT3"); // mixed case + BOOST_CHECK(flags.test(TestEnum::Bit3)); + } + + { + EFlags flags("bit1|BIT2|BiT3"); // mixed case with delimiter + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + } + + // Test special keywords case-insensitive + { + EFlags flags("ALL"); + BOOST_CHECK(flags.all()); + } + + { + EFlags flags("None"); + BOOST_CHECK(!flags.any()); + } +} + +BOOST_AUTO_TEST_CASE(Flags_error_recovery_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test that previous state is restored on exception + { + EFlags flags({TestEnum::Bit1, TestEnum::Bit2}); + auto previousValue = flags.value(); + + // Try to set with invalid string + BOOST_CHECK_THROW(flags.set("InvalidFlag"), std::invalid_argument); + + // Verify state was restored + BOOST_CHECK_EQUAL(flags.value(), previousValue); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + } + + { + EFlags flags({TestEnum::Bit3, TestEnum::Bit4}); + auto previousValue = flags.value(); + + // Try to set with out-of-range value + BOOST_CHECK_THROW(flags.set("999999", 10), std::out_of_range); + + // Verify state was restored + BOOST_CHECK_EQUAL(flags.value(), previousValue); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + BOOST_CHECK(flags.test(TestEnum::Bit4)); + } + + { + EFlags flags(TestEnum::Bit5VeryLongName); + auto previousValue = flags.value(); + + // Try to set with invalid binary string + BOOST_CHECK_THROW(flags.set("10102", 2), std::invalid_argument); + + // Verify state was restored + BOOST_CHECK_EQUAL(flags.value(), previousValue); + BOOST_CHECK(flags.test(TestEnum::Bit5VeryLongName)); + } +} + +BOOST_AUTO_TEST_CASE(Flags_whitespace_handling_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test leading/trailing whitespace + { + EFlags flags(" Bit1 "); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + } + + { + EFlags flags(" Bit1 | Bit2 "); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + } + + // Test excessive whitespace between flags + { + EFlags flags("Bit1 | Bit3"); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + BOOST_CHECK(!flags.test(TestEnum::Bit2)); + } + + // Test tabs and other whitespace (should work with space delimiter) + { + EFlags flags("Bit1 Bit2 Bit3"); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + } +} + +BOOST_AUTO_TEST_CASE(Flags_count_bits_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test counting set bits + { + EFlags flags; + BOOST_CHECK_EQUAL(flags.count(), 0); + } + + { + EFlags flags(TestEnum::Bit1); + BOOST_CHECK_EQUAL(flags.count(), 1); + } + + { + EFlags flags({TestEnum::Bit1, TestEnum::Bit2}); + BOOST_CHECK_EQUAL(flags.count(), 2); + } + + { + EFlags flags({TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3, TestEnum::Bit4}); + BOOST_CHECK_EQUAL(flags.count(), 4); + } + + { + EFlags flags(EFlags::All); + BOOST_CHECK_EQUAL(flags.count(), 5); // TestEnum has 5 members + } + + // Test count after operations + { + EFlags flags({TestEnum::Bit1, TestEnum::Bit2, TestEnum::Bit3}); + BOOST_CHECK_EQUAL(flags.count(), 3); + + flags.reset(TestEnum::Bit2); + BOOST_CHECK_EQUAL(flags.count(), 2); + + flags.set(TestEnum::Bit4); + BOOST_CHECK_EQUAL(flags.count(), 3); + + flags.toggle(TestEnum::Bit1); + BOOST_CHECK_EQUAL(flags.count(), 2); + } +} + +BOOST_AUTO_TEST_CASE(Flags_mixed_delimiter_validation_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test that mixed delimiters throw an error + { + BOOST_CHECK_THROW(EFlags("Bit1|Bit2,Bit3"), std::invalid_argument); + } + + { + BOOST_CHECK_THROW(EFlags("Bit1;Bit2|Bit3"), std::invalid_argument); + } + + { + BOOST_CHECK_THROW(EFlags("Bit1,Bit2;Bit3"), std::invalid_argument); + } + + { + BOOST_CHECK_THROW(EFlags("Bit1|Bit2,Bit3;Bit4"), std::invalid_argument); + } + + // Test that single delimiter types work + { + EFlags flags1("Bit1|Bit2|Bit3"); + BOOST_CHECK_EQUAL(flags1.count(), 3); + } + + { + EFlags flags2("Bit1,Bit2,Bit3"); + BOOST_CHECK_EQUAL(flags2.count(), 3); + } + + { + EFlags flags3("Bit1;Bit2;Bit3"); + BOOST_CHECK_EQUAL(flags3.count(), 3); + } +} + +BOOST_AUTO_TEST_CASE(Flags_empty_and_edge_cases_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test empty string + { + EFlags flags({TestEnum::Bit1, TestEnum::Bit2}); + flags.set(""); // Should be no-op + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + } + + // Test with only whitespace + { + EFlags flags({TestEnum::Bit1}); + flags.set(" "); // Should result in empty after tokenization + // Depending on implementation, this might clear or throw + // Adjust expectation based on actual behavior + } + + // Test duplicate flags (should work, setting same bit twice is idempotent) + { + EFlags flags("Bit1|Bit1|Bit1"); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK_EQUAL(flags.count(), 1); + } + + // Test scoped and unscoped mixed + { + EFlags flags("Bit1|TestEnum::Bit2"); + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + } +} + +BOOST_AUTO_TEST_CASE(Flags_binary_decimal_parsing_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test binary parsing + { + EFlags flags("101", 2); + BOOST_CHECK(flags.test(TestEnum::Bit1)); // bit 0 + BOOST_CHECK(!flags.test(TestEnum::Bit2)); // bit 1 + BOOST_CHECK(flags.test(TestEnum::Bit3)); // bit 2 + } + + // Test decimal parsing + { + EFlags flags("7", 10); // 7 = 0b111 + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + BOOST_CHECK(!flags.test(TestEnum::Bit4)); + } + + // Test hexadecimal parsing + { + EFlags flags("F", 16); // 15 = 0b1111 + BOOST_CHECK(flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(flags.test(TestEnum::Bit3)); + BOOST_CHECK(flags.test(TestEnum::Bit4)); + BOOST_CHECK(!flags.test(TestEnum::Bit5VeryLongName)); + } + + // Test hexadecimal with 0x prefix + { + EFlags flags("0xA", 16); // 10 = 0b1010 + BOOST_CHECK(!flags.test(TestEnum::Bit1)); + BOOST_CHECK(flags.test(TestEnum::Bit2)); + BOOST_CHECK(!flags.test(TestEnum::Bit3)); + BOOST_CHECK(flags.test(TestEnum::Bit4)); + } + + // Test hexadecimal with 0X prefix (uppercase) + { + EFlags flags("0X1F", 16); // 31 = all 5 bits + BOOST_CHECK(flags.all()); + } + + // Test lowercase hex digits + { + EFlags flags("0xa", 16); + BOOST_CHECK_EQUAL(flags.value(), 10); + } + + // Test thros + { + BOOST_CHECK_THROW(EFlags("0xAbCd", 16), std::out_of_range); + } + + // Test invalid binary string (contains 2) + { + BOOST_CHECK_THROW(EFlags("1012", 2), std::invalid_argument); + } + + // Test out of range for base + { + BOOST_CHECK_THROW(EFlags("100000", 2), std::out_of_range); + } +} + +BOOST_AUTO_TEST_CASE(Flags_operator_bool_test) +{ + using EFlags = o2::utils::EnumFlags; + + // Test explicit bool conversion + { + EFlags empty; + BOOST_CHECK(!static_cast(empty)); + } + + { + EFlags withFlag(TestEnum::Bit1); + BOOST_CHECK(static_cast(withFlag)); + } + + // Test in conditional + { + EFlags flags; + if (flags) { + BOOST_FAIL("Empty flags should be false"); + } + + flags.set(TestEnum::Bit1); + if (!flags) { + BOOST_FAIL("Non-empty flags should be true"); + } + } } diff --git a/DataFormats/Detectors/CTP/include/DataFormatsCTP/Configuration.h b/DataFormats/Detectors/CTP/include/DataFormatsCTP/Configuration.h index e9464089d71fc..ff1462084d53d 100644 --- a/DataFormats/Detectors/CTP/include/DataFormatsCTP/Configuration.h +++ b/DataFormats/Detectors/CTP/include/DataFormatsCTP/Configuration.h @@ -214,7 +214,8 @@ struct CtpCfg { uint32_t orbitShift = 0; uint32_t irInputs_1_24 = 0; uint32_t irInputs_25_48 = 0; - ClassDefNV(CtpCfg, 1) + std::vector listOfUsedInputs(); + ClassDefNV(CtpCfg, 2) }; } // namespace ctp } // namespace o2 diff --git a/DataFormats/Detectors/CTP/src/CTPRateFetcher.cxx b/DataFormats/Detectors/CTP/src/CTPRateFetcher.cxx index 5f31fe5741240..939203d168604 100644 --- a/DataFormats/Detectors/CTP/src/CTPRateFetcher.cxx +++ b/DataFormats/Detectors/CTP/src/CTPRateFetcher.cxx @@ -233,9 +233,7 @@ double CTPRateFetcher::pileUpCorrection(double triggerRate) if (mLHCIFdata.getFillNumber() == 0) { LOG(fatal) << "No filling" << std::endl; } - auto bfilling = mLHCIFdata.getBunchFilling(); - std::vector bcs = bfilling.getFilledBCs(); - double nbc = bcs.size(); + double nbc = mLHCIFdata.getBunchFilling().getPattern().count(); double nTriggersPerFilledBC = triggerRate / nbc / constants::lhc::LHCRevFreq; double mu = -std::log(1 - nTriggersPerFilledBC); return mu * nbc * constants::lhc::LHCRevFreq; diff --git a/DataFormats/Detectors/CTP/src/Configuration.cxx b/DataFormats/Detectors/CTP/src/Configuration.cxx index 61e51bcb20d91..98458ef06d1d3 100644 --- a/DataFormats/Detectors/CTP/src/Configuration.cxx +++ b/DataFormats/Detectors/CTP/src/Configuration.cxx @@ -1227,9 +1227,24 @@ int CtpCfg::readAndSave(std::string& path) } return 0; } - +std::vector CtpCfg::listOfUsedInputs() +{ + std::cout << std::hex << "0x" << irInputs_1_24 << " " << irInputs_25_48 << std::dec << std::endl; + std::vector inputList; + for (int i = 0; i < 24; i++) { + if ((1ul << i) & irInputs_1_24) { + inputList.push_back(i); + } + } + for (int i = 0; i < 24; i++) { + if ((1ul << i) & irInputs_25_48) { + inputList.push_back(i + 24); + } + } + return inputList; +} std::ostream& o2::ctp::operator<<(std::ostream& in, const o2::ctp::CTPConfiguration& conf) { conf.printStream(in); return in; -} +} \ No newline at end of file diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/DetID.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/DetID.h index a2767c7620cdd..2d2383783cfc3 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/DetID.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/DetID.h @@ -87,7 +87,8 @@ class DetID static constexpr ID RCH = 23; static constexpr ID MI3 = 24; static constexpr ID ECL = 25; - static constexpr ID Last = ECL; + static constexpr ID FD3 = 26; + static constexpr ID Last = FD3; #else static constexpr ID Last = FOC; ///< if extra detectors added, update this !!! #endif @@ -181,7 +182,7 @@ class DetID // detector names, will be defined in DataSources static constexpr const char* sDetNames[nDetectors + 1] = ///< defined detector names #ifdef ENABLE_UPGRADES - {"ITS", "TPC", "TRD", "TOF", "PHS", "CPV", "EMC", "HMP", "MFT", "MCH", "MID", "ZDC", "FT0", "FV0", "FDD", "TST", "CTP", "FOC", "IT3", "TRK", "FT3", "FCT", "TF3", "RCH", "MI3", "ECL", nullptr}; + {"ITS", "TPC", "TRD", "TOF", "PHS", "CPV", "EMC", "HMP", "MFT", "MCH", "MID", "ZDC", "FT0", "FV0", "FDD", "TST", "CTP", "FOC", "IT3", "TRK", "FT3", "FCT", "TF3", "RCH", "MI3", "ECL", "FD3", nullptr}; #else {"ITS", "TPC", "TRD", "TOF", "PHS", "CPV", "EMC", "HMP", "MFT", "MCH", "MID", "ZDC", "FT0", "FV0", "FDD", "TST", "CTP", "FOC", nullptr}; #endif @@ -195,7 +196,7 @@ class DetID #ifdef ENABLE_UPGRADES , o2h::gDataOriginIT3, o2h::gDataOriginTRK, o2h::gDataOriginFT3, o2h::gDataOriginFCT, o2h::gDataOriginTF3, - o2h::gDataOriginRCH, o2h::gDataOriginMI3, o2h::gDataOriginECL + o2h::gDataOriginRCH, o2h::gDataOriginMI3, o2h::gDataOriginECL, o2h::gDataOriginFD3 #endif }; #endif // GPUCA_GPUCODE_DEVICE @@ -211,10 +212,11 @@ GPUconstexpr() DetID::mask_t sMasks[DetID::nDetectors] = ///< detectot masks DetID::mask_t(math_utils::bit2Mask(DetID::CPV)), DetID::mask_t(math_utils::bit2Mask(DetID::EMC)), DetID::mask_t(math_utils::bit2Mask(DetID::HMP)), DetID::mask_t(math_utils::bit2Mask(DetID::MFT)), DetID::mask_t(math_utils::bit2Mask(DetID::MCH)), DetID::mask_t(math_utils::bit2Mask(DetID::MID)), DetID::mask_t(math_utils::bit2Mask(DetID::ZDC)), DetID::mask_t(math_utils::bit2Mask(DetID::FT0)), DetID::mask_t(math_utils::bit2Mask(DetID::FV0)), DetID::mask_t(math_utils::bit2Mask(DetID::FDD)), DetID::mask_t(math_utils::bit2Mask(DetID::TST)), DetID::mask_t(math_utils::bit2Mask(DetID::CTP)), DetID::mask_t(math_utils::bit2Mask(DetID::FOC)) + #ifdef ENABLE_UPGRADES , DetID::mask_t(math_utils::bit2Mask(DetID::IT3)), DetID::mask_t(math_utils::bit2Mask(DetID::TRK)), DetID::mask_t(math_utils::bit2Mask(DetID::FT3)), DetID::mask_t(math_utils::bit2Mask(DetID::FCT)), DetID::mask_t(math_utils::bit2Mask(DetID::TF3)), - DetID::mask_t(math_utils::bit2Mask(DetID::RCH)), DetID::mask_t(math_utils::bit2Mask(DetID::MI3)), DetID::mask_t(math_utils::bit2Mask(DetID::ECL)) + DetID::mask_t(math_utils::bit2Mask(DetID::RCH)), DetID::mask_t(math_utils::bit2Mask(DetID::MI3)), DetID::mask_t(math_utils::bit2Mask(DetID::ECL)), DetID::mask_t(math_utils::bit2Mask(DetID::FD3)) #endif }; } // namespace detid_internal diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/EncodedBlocks.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/EncodedBlocks.h index 6fb8825f7c395..ba6b853f7fb23 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/EncodedBlocks.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/EncodedBlocks.h @@ -468,6 +468,9 @@ class EncodedBlocks /// total allocated size in bytes size_t size() const { return mRegistry.size; } + /// used part of total allocated size in bytes (output size) + size_t outputsize() const { return mRegistry.offsFreeStart; } + /// size remaining for additional data size_t getFreeSize() const { return mRegistry.getFreeSize(); } @@ -899,7 +902,7 @@ void EncodedBlocks::print(const std::string& prefix, int verbosity) con ndata += mBlocks[i].getNData(); nlit += mBlocks[i].getNLiterals(); } - LOG(info) << prefix << N << " blocks, input size: " << inpSize << ", output size: " << size() + LOG(info) << prefix << N << " blocks, input size: " << inpSize << ", output size: " << outputsize() << " NDictWords: " << ndict << " NDataWords: " << ndata << " NLiteralWords: " << nlit; } } @@ -929,9 +932,11 @@ CTFIOSize EncodedBlocks::decode(D_IT dest, // it const auto& md = mMetadata[slot]; LOGP(debug, "Slot{} | NStored={} Ndict={} nData={}, MD: messageLength:{} opt:{} min:{} max:{} offs:{} width:{} ", slot, block.getNStored(), block.getNDict(), block.getNData(), md.messageLength, (int)md.opt, md.min, md.max, md.literalsPackingOffset, md.literalsPackingWidth); + constexpr size_t word_size = sizeof(W); + if (ansVersion == ANSVersionCompat) { if (!block.getNStored()) { - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * word_size}; } if (md.opt == Metadata::OptStore::EENCODE) { return decodeCompatImpl(dest, slot, decoderExt); @@ -943,7 +948,7 @@ CTFIOSize EncodedBlocks::decode(D_IT dest, // it return decodeUnpackImpl(dest, slot); } if (!block.getNStored()) { - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * word_size}; } if (md.opt == Metadata::OptStore::EENCODE) { return decodeRansV1Impl(dest, slot, decoderExt); @@ -991,7 +996,7 @@ CTFIOSize EncodedBlocks::decodeCompatImpl(dst_IT dstBegin, int slot, co } else { getDecoder().process(block.getData() + block.getNData(), dstBegin, md.messageLength, NDecoderStreams); } - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * sizeof(W)}; }; template @@ -1045,7 +1050,7 @@ CTFIOSize EncodedBlocks::decodeRansV1Impl(dst_IT dstBegin, int slot, co } else { getDecoder().process(block.getData() + block.getNData(), dstBegin, md.messageLength, md.nStreams); } - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * sizeof(W)}; }; template @@ -1079,7 +1084,7 @@ CTFIOSize EncodedBlocks::decodeUnpackImpl(dst_IT dest, int slot) const } else { rans::unpack(srcIt, messageLength, dest, packingWidth, offset); } - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * sizeof(W)}; }; template @@ -1098,7 +1103,7 @@ CTFIOSize EncodedBlocks::decodeCopyImpl(dst_IT dest, int slot) const destPtr_t srcEnd = srcBegin + md.messageLength * sizeof(dest_t); std::copy(srcBegin, srcEnd, dest); - return {0, md.getUncompressedSize(), md.getCompressedSize()}; + return {0, md.getUncompressedSize(), md.getCompressedSize() * sizeof(W)}; }; ///_____________________________________________________________________________ @@ -1268,7 +1273,7 @@ o2::ctf::CTFIOSize EncodedBlocks::entropyCodeRANSCompat(const input_IT dataSize, nLiteralWords); - return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize()}; + return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize() * sizeof(W)}; } template @@ -1349,7 +1354,7 @@ CTFIOSize EncodedBlocks::encodeRANSV1External(const input_IT srcBegin, dataSize, literalsSize); - return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize()}; + return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize() * sizeof(W)}; }; template @@ -1458,7 +1463,7 @@ CTFIOSize EncodedBlocks::encodeRANSV1Inplace(const input_IT srcBegin, c dataSize, literalsSize); - return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize()}; + return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize() * sizeof(W)}; }; // namespace ctf template @@ -1491,7 +1496,7 @@ o2::ctf::CTFIOSize EncodedBlocks::pack(const input_IT srcBegin, const i } LOGP(debug, "StoreData {} bytes, offs: {}:{}", packedSize * sizeof(storageBuffer_t), thisBlock->getOffsData(), thisBlock->getOffsData() + packedSize * sizeof(storageBuffer_t)); - return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize()}; + return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize() * sizeof(W)}; }; template @@ -1513,7 +1518,7 @@ o2::ctf::CTFIOSize EncodedBlocks::store(const input_IT srcBegin, const *thisMetadata = detail::makeMetadataStore(messageLength, opt, nBufferElems); - return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize()}; + return {0, thisMetadata->getUncompressedSize(), thisMetadata->getCompressedSize() * sizeof(W)}; }; /// create a special EncodedBlocks containing only dictionaries made from provided vector of frequency tables diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/Metadata.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/Metadata.h index abf7561eb25a9..975522767dce1 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/Metadata.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/Metadata.h @@ -37,7 +37,7 @@ struct Metadata { size_t nLiterals = 0; // Number of samples that were stored as literals. uint8_t messageWordSize = 0; // size in Bytes of a symbol in the encoded message. uint8_t coderType = 0; // what type of CTF Coder is used? (32 vs 64 bit coders). - uint8_t streamSize = 0; // how many Bytes is the rANS encoder emmiting during a stream-out step. + uint8_t streamSize = 0; // number of Bytes emmitted during rANS stream out (ransCompat) or lower renorming bound (ransV1). uint8_t probabilityBits = 0; // The encoder renormed the distribution of source symbols to sum up to 2^probabilityBits. OptStore opt = OptStore::EENCODE; // The type of storage operation that was conducted. int32_t min = 0; // min symbol of the source dataset. @@ -48,8 +48,21 @@ struct Metadata { int nDataWords = 0; // Amount of words used to store the actual data. int nLiteralWords = 0; // Amount of words used to store literal (incompressible) samples. + /** + * @brief Uncompressed size of stored data in bytes + * + * @return size_t Uncompressed size in bytes + */ size_t getUncompressedSize() const { return messageLength * messageWordSize; } - size_t getCompressedSize() const { return (nDictWords + nDataWords + nLiteralWords) * streamSize; } + + /** + * @brief Size of the stored, compressed data in multiples of the underlying buffer word size + * + * @return size_t The size in multiples of the underlying buffer word size + * @warning This size is in number of words of the underlying storage buffer. + * Multiply with the size of the storage buffer type to get the correct size in bytes. + */ + size_t getCompressedSize() const { return nDictWords + nDataWords + nLiteralWords; } void clear() { nStreams = 0; diff --git a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h index 8f9cbcfbdba43..37c4b790d181b 100644 --- a/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h +++ b/DataFormats/Detectors/Common/include/DetectorsCommonDataFormats/SimTraits.h @@ -99,7 +99,8 @@ class SimTraits /*TF3*/ VS{ "TF3Hit" }, /*RCH*/ VS{ "RCHHit" }, /*MI3*/ VS{ "MI3Hit" }, - /*ECL*/ VS{ "ECLHit" } + /*ECL*/ VS{ "ECLHit" }, + /*FD */ VS{ "FDHit" } #endif }; // clang-format on diff --git a/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/EMCALChannelData.h b/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/EMCALChannelData.h deleted file mode 100644 index 3c014d37e6f9e..0000000000000 --- a/DataFormats/Detectors/EMCAL/include/DataFormatsEMCAL/EMCALChannelData.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 EMCALChannelData.h -/// \brief - -/// \class EMCALChannelCalibrator -/// \brief Class to store the data format for calibraton of the EMCal -/// \author Hannah Bossi, Yale University -/// \ingroup DetectorEMCAL -/// \since Feb 11, 2021 - -#ifndef ALICEO2_EMCALCHANNELDATA_H -#define ALICEO2_EMCALCHANNELDATA_H - -#include "Rtypes.h" - -namespace o2 -{ -namespace dataformats -{ -class EMCALChannelData -{ - public: - EMCALChannelData(int cellID, int timestamp, int flags = 0, int events) : mEMCALCellID(cellID), mTimestamp(timestamp), mFlags(flags){}; - EMCALChannelData() = default; - ~EMCALChannelData() = default; - - void setEMCALCellID(int index) { mEMCALCellID = index; } - int getEMCALCellID() const { return mEMCALCellID; } - - void setTimestamp(int ts) { mTimestamp = ts; } - int getTimestamp() const { return mTimestamp; } - - void setFlags(int flags) { mFlags = flags; } - float getFlags() const { return mFlags; } - - private: - int mEMCALCellID; ///< EMCal Cell ID - int mTimestamp; ///< timestamp in seconds - unsigned char mFlags; ///< bit mask with quality flags (to be defined) - - ClassDefNV(EMCALChannelData, 1); -}; -} // namespace dataformats -} // namespace o2 -#endif diff --git a/DataFormats/Detectors/FIT/FT0/CMakeLists.txt b/DataFormats/Detectors/FIT/FT0/CMakeLists.txt index e5331b7b739b2..f7d6a111f4348 100644 --- a/DataFormats/Detectors/FIT/FT0/CMakeLists.txt +++ b/DataFormats/Detectors/FIT/FT0/CMakeLists.txt @@ -47,4 +47,5 @@ o2_target_root_dictionary(DataFormatsFT0 include/DataFormatsFT0/GlobalOffsetsCalibrationObject.h include/DataFormatsFT0/SpectraInfoObject.h include/DataFormatsFT0/SlewingCoef.h + include/DataFormatsFT0/EventsPerBc.h ) 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 new file mode 100644 index 0000000000000..632eac342fdc9 --- /dev/null +++ b/DataFormats/Detectors/FIT/FT0/include/DataFormatsFT0/EventsPerBc.h @@ -0,0 +1,38 @@ +// 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 _FT0_EVENTS_PER_BC_CALIB_OBJECT +#define _FT0_EVENTS_PER_BC_CALIB_OBJECT + +#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 +#endif \ No newline at end of file diff --git a/DataFormats/Detectors/FIT/FT0/src/DataFormatsFT0LinkDef.h b/DataFormats/Detectors/FIT/FT0/src/DataFormatsFT0LinkDef.h index 0d3491224180c..7f8c17a0cd191 100644 --- a/DataFormats/Detectors/FIT/FT0/src/DataFormatsFT0LinkDef.h +++ b/DataFormats/Detectors/FIT/FT0/src/DataFormatsFT0LinkDef.h @@ -56,4 +56,6 @@ #pragma link C++ class std::pair < std::vector < double>, std::vector < double>> + ; #pragma link C++ class o2::ft0::SlewingCoef + ; +#pragma link C++ class o2::ft0::EventsPerBc + ; + #endif 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 61% rename from Detectors/ITSMFT/common/base/include/ITSMFTBase/DPLAlpideParam.h rename to DataFormats/Detectors/ITSMFT/common/include/DataFormatsITSMFT/DPLAlpideParam.h index bc3b3dbde53b0..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" @@ -26,17 +26,30 @@ constexpr float DEFStrobeDelay = o2::constants::lhc::LHCBunchSpacingNS * 4; // ~ template struct DPLAlpideParam : public o2::conf::ConfigurableParamHelper> { + static constexpr int getNLayers() + { + return N == o2::detectors::DetID::ITS ? 7 : 10; + } static constexpr std::string_view getParamName() { return N == o2::detectors::DetID::ITS ? ParamName[0] : ParamName[1]; } - int roFrameLengthInBC = DEFROFLengthBC(); ///< ROF length in BC for continuos mode - float roFrameLengthTrig = DEFROFLengthTrig(); ///< length of RO frame in ns for triggered mode - float strobeDelay = DEFStrobeDelay; ///< strobe start (in ns) wrt ROF start - float strobeLengthCont = -1.; ///< if < 0, full ROF length - delay - float strobeLengthTrig = 100.; ///< length of the strobe in ns (sig. over threshold checked in this window only) - int roFrameBiasInBC = DEFROFBiasInBC(); ///< bias of the start of ROF wrt orbit start: t_irof = (irof*roFrameLengthInBC + roFrameBiasInBC)*BClengthMUS + + int roFrameLengthInBC = DEFROFLengthBC(); ///< ROF length in BC for continuous mode + float roFrameLengthTrig = DEFROFLengthTrig(); ///< length of RO frame in ns for triggered mode + float strobeDelay = DEFStrobeDelay; ///< strobe start (in ns) wrt ROF start + float strobeLengthCont = -1.; ///< if < 0, full ROF length - delay + float strobeLengthTrig = 100.; ///< length of the strobe in ns (sig. over threshold checked in this window only) + int roFrameBiasInBC = DEFROFBiasInBC(); ///< bias of the start of ROF wrt orbit start: t_irof = (irof*roFrameLengthInBC + roFrameBiasInBC)*BClengthMUS + int roFrameLayerLengthInBC[getNLayers()] = {}; ///< staggering ROF length in BC for continuous mode per layer + int roFrameLayerBiasInBC[getNLayers()] = {}; ///< staggering ROF bias in BC for continuous mode per layer + int roFrameLayerDelayInBC[getNLayers()] = {}; ///< staggering ROF delay in BC for continuous mode per layer + + // get ROF length for any layer + int getROFLengthInBC(int layer) const noexcept { return roFrameLayerLengthInBC[layer] ? roFrameLayerLengthInBC[layer] : roFrameLengthInBC; } + int getROFBiasInBC(int layer) const noexcept { return roFrameLayerBiasInBC[layer] ? roFrameLayerBiasInBC[layer] : roFrameBiasInBC; } + int getROFDelayInBC(int layer) const noexcept { return roFrameLayerDelayInBC[layer] ? roFrameLayerDelayInBC[layer] : 0; } // boilerplate stuff + make principal key O2ParamDef(DPLAlpideParam, getParamName().data()); @@ -46,7 +59,7 @@ 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/ITSMFT/common/src/ROFRecord.cxx b/DataFormats/Detectors/ITSMFT/common/src/ROFRecord.cxx index 83b46f8798fc9..8dbde0d580efc 100644 --- a/DataFormats/Detectors/ITSMFT/common/src/ROFRecord.cxx +++ b/DataFormats/Detectors/ITSMFT/common/src/ROFRecord.cxx @@ -9,20 +9,22 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "DataFormatsITSMFT/ROFRecord.h" #include -#include "fmt/format.h" +#include + +#include "DataFormatsITSMFT/ROFRecord.h" +#include "Framework/Logger.h" using namespace o2::itsmft; std::string ROFRecord::asString() const { - return fmt::format("ROF: {} | {} entries starting from {}", mROFrame, getNEntries(), getFirstEntry()); + return std::format("ROF: {} | {} entries starting from {} | IR: {}", mROFrame, getNEntries(), getFirstEntry(), mBCData.asString()); } void ROFRecord::print() const { - std::cout << this << "\n\t" << mBCData << std::endl; + LOG(info) << asString(); } std::ostream& operator<<(std::ostream& stream, ROFRecord const& rec) @@ -33,12 +35,12 @@ std::ostream& operator<<(std::ostream& stream, ROFRecord const& rec) std::string MC2ROFRecord::asString() const { - return fmt::format("MCEventID: {} ROFs: {}-{} Entry in ROFRecords: {}", eventRecordID, minROF, maxROF, rofRecordID); + return std::format("MCEventID: {} ROFs: {}-{} Entry in ROFRecords: {}", eventRecordID, minROF, maxROF, rofRecordID); } void MC2ROFRecord::print() const { - std::cout << this << std::endl; + LOG(info) << asString(); } std::ostream& operator<<(std::ostream& stream, MC2ROFRecord const& rec) diff --git a/DataFormats/Detectors/TOF/CMakeLists.txt b/DataFormats/Detectors/TOF/CMakeLists.txt index 8a55e531287e1..4d41167f7bf1d 100644 --- a/DataFormats/Detectors/TOF/CMakeLists.txt +++ b/DataFormats/Detectors/TOF/CMakeLists.txt @@ -9,6 +9,14 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. +o2_add_library(DataFormatsParamTOF + SOURCES src/ParameterContainers.cxx + PUBLIC_LINK_LIBRARIES O2::FrameworkLogger) + + +o2_target_root_dictionary(DataFormatsParamTOF + HEADERS include/DataFormatsTOF/ParameterContainers.h) + o2_add_library(DataFormatsTOF SOURCES src/Cluster.cxx src/CalibInfoTOFshort.cxx @@ -22,6 +30,7 @@ o2_add_library(DataFormatsTOF src/TOFFEElightInfo.cxx PUBLIC_LINK_LIBRARIES O2::ReconstructionDataFormats O2::GPUCommon + O2::DataFormatsParamTOF Boost::serialization) o2_target_root_dictionary(DataFormatsTOF @@ -33,7 +42,6 @@ o2_target_root_dictionary(DataFormatsTOF include/DataFormatsTOF/RawDataFormat.h include/DataFormatsTOF/CompressedDataFormat.h include/DataFormatsTOF/CTF.h - include/DataFormatsTOF/ParameterContainers.h include/DataFormatsTOF/CalibInfoCluster.h include/DataFormatsTOF/CosmicInfo.h include/DataFormatsTOF/TOFFEElightInfo.h diff --git a/DataFormats/Detectors/TOF/include/DataFormatsTOF/Cluster.h b/DataFormats/Detectors/TOF/include/DataFormatsTOF/Cluster.h index 2f15923419795..37d3ca23ddb35 100644 --- a/DataFormats/Detectors/TOF/include/DataFormatsTOF/Cluster.h +++ b/DataFormats/Detectors/TOF/include/DataFormatsTOF/Cluster.h @@ -49,9 +49,8 @@ class Cluster : public o2::BaseCluster kDownRight = 4, // 2^4, 5th bit kDown = 5, // 2^5, 6th bit kDownLeft = 6, // 2^6, 7th bit - kLeft = 7, // 2^7, 8th bit - // - FrameBit = 6 }; // this bit set means that the cluster is in the nominal (alpha=20*sector+10 deg.) sector frame rather than aligned + kLeft = 7 // 2^7, 8th bit + }; Cluster() = default; @@ -59,8 +58,8 @@ class Cluster : public o2::BaseCluster ~Cluster() = default; - bool isInNominalSector() const { return isBitSet(FrameBit); } - void setInNominalSector() { setBit(FrameBit); } + bool isInNominalSector() const { return mInNominalSector; } + void setInNominalSector(bool v = true) { mInNominalSector = v; } std::int8_t getSector() const { return getCount(); } void setSector(std::int8_t value) { setCount(value); } @@ -163,9 +162,10 @@ class Cluster : public o2::BaseCluster double mDigitInfoT[6] = {0., 0., 0., 0., 0., 0.}; float mDigitInfoTOT[6] = {0., 0., 0., 0., 0., 0.}; float mTgeant = 0.0; + bool mInNominalSector = false; double mT0true = 0.0; - ClassDefNV(Cluster, 5); + ClassDefNV(Cluster, 6); }; #ifndef GPUCA_GPUCODE diff --git a/DataFormats/Detectors/TOF/include/DataFormatsTOF/Diagnostic.h b/DataFormats/Detectors/TOF/include/DataFormatsTOF/Diagnostic.h index 8adcdb63e9d21..028da04b3ef70 100644 --- a/DataFormats/Detectors/TOF/include/DataFormatsTOF/Diagnostic.h +++ b/DataFormats/Detectors/TOF/include/DataFormatsTOF/Diagnostic.h @@ -44,6 +44,13 @@ class Diagnostic uint32_t fillEmptyTOF(uint32_t frequency = 1) { return fill(1, frequency); } static ULong64_t getEmptyCrateKey(int crate); static ULong64_t getNoisyChannelKey(int channel); + static ULong64_t getDRMKey(int crate) { return 1000000 + crate * 1000; } + static ULong64_t getDRMerrorKey(int crate, int error) { return getDRMKey(crate) + error; } + uint32_t getFrequencyDRM(int crate) const { return getFrequency(getDRMKey(crate)); } + uint32_t getFrequencyDRMerror(int crate, int error) const { return getFrequency(getDRMerrorKey(crate, error)); } + uint32_t fillDRM(int crate, uint32_t frequency) { return fill(getDRMKey(crate), frequency); } + uint32_t fillDRMerror(int crate, int error, uint32_t frequency) { return fill(getDRMerrorKey(crate, error), frequency); } + static ULong64_t getTRMKey(int crate, int trm); void print(bool longFormat = false) const; void clear() { mVector.clear(); } diff --git a/DataFormats/Detectors/TOF/include/DataFormatsTOF/ParameterContainers.h b/DataFormats/Detectors/TOF/include/DataFormatsTOF/ParameterContainers.h index c9d910d8345e5..224906e43b8c6 100644 --- a/DataFormats/Detectors/TOF/include/DataFormatsTOF/ParameterContainers.h +++ b/DataFormats/Detectors/TOF/include/DataFormatsTOF/ParameterContainers.h @@ -18,10 +18,10 @@ #ifndef O2_TOF_PARAMCONTAINER_H #define O2_TOF_PARAMCONTAINER_H -#include "TNamed.h" -#include "TFile.h" -#include "Framework/Logger.h" -#include "map" +#include +#include +#include +#include namespace o2 { @@ -37,7 +37,7 @@ class Parameters Parameters(std::array parNames, std::string name) : mName{name}, mPar{}, mParNames{parNames} {}; /// Default destructor - virtual ~Parameters() = default; // Ensure proper cleanup in derived classes + ~Parameters() = default; /// Setter for the parameter at position iparam /// \param iparam index in the array of the parameters @@ -183,27 +183,10 @@ class ParameterCollection : public TNamed /// @param value parameter to add to the stored information /// @param pass key to look for in the stored information e.g. pass /// @return true if found and configured false if not fully configured - bool addParameter(const std::string& pass, const std::string& parName, float value) - { - const bool alreadyPresent = hasKey(pass); - if (alreadyPresent) { - LOG(debug) << "Changing parametrization corresponding to key " << pass << " from size " << mParameters[pass].size() << " to " << parName; - } else { - mParameters[pass] = std::unordered_map{}; - LOG(debug) << "Adding new parametrization corresponding to key " << pass << ": " << parName; - } - mParameters[pass][parName] = value; - return true; - } + bool addParameter(const std::string& pass, const std::string& parName, float value); /// @return the size of the container i.e. the number of stored keys (or passes) - int getSize(const std::string& pass) const - { - if (!hasKey(pass)) { - return -1; - } - return mParameters.at(pass).size(); - } + int getSize(const std::string& pass) const; /// @brief Function to push the parameters from the sub container into the collection and store it under a given key /// @tparam ParType type of the parameter container @@ -227,34 +210,24 @@ class ParameterCollection : public TNamed } /// @brief getter for the parameters stored in the container matching to a pass - const auto& getPars(const std::string& pass) const { return mParameters.at(pass); } - - /// @brief printing function for the content of the pass - /// @param pass pass to print - void print(const std::string& pass) const + const std::unordered_map& getPars(const std::string& pass) const { - const auto& size = getSize(pass); - if (size < 0) { - LOG(info) << "empty pass: " << pass; - return; - } - LOG(info) << "Pass \"" << pass << "\" with size " << size; - for (const auto& [par, value] : mParameters.at(pass)) { - LOG(info) << "par name = " << par << ", value = " << value; + if (!hasKey(pass)) { + LOG(fatal) << "Parameters for pass " << pass << " not found!"; } + return mParameters.at(pass); } + /// @brief printing function for the content of the pass + /// @param pass pass to print + void print(const std::string& pass) const; + /// @brief printing function for the full content of the container - void print() const - { - for (const auto& [pass, pars] : mParameters) { - print(pass); - } - } + void print() const; /// @brief Getter of the full map of parameters stored in the container /// @return returns the full map of parameters - const auto& getFullMap() { return mParameters; } + const std::unordered_map>& getFullMap() const { return mParameters; } /// Loader from file /// \param FileName name of the input file diff --git a/GPU/TPCFastTransformation/MultivariatePolynomial.cxx b/DataFormats/Detectors/TOF/src/DataFormatsParamTOFLinkDef.h similarity index 80% rename from GPU/TPCFastTransformation/MultivariatePolynomial.cxx rename to DataFormats/Detectors/TOF/src/DataFormatsParamTOFLinkDef.h index b1ffe616fb65e..2d6ee84bedb92 100644 --- a/GPU/TPCFastTransformation/MultivariatePolynomial.cxx +++ b/DataFormats/Detectors/TOF/src/DataFormatsParamTOFLinkDef.h @@ -9,7 +9,9 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file MultivariatePolynomial.cxx -/// \author Matthias Kleiner +#ifdef __CLING__ -#include "MultivariatePolynomial.h" +#pragma link C++ class o2::tof::Parameters < 5> + ; +#pragma link C++ class o2::tof::ParameterCollection + ; + +#endif diff --git a/DataFormats/Detectors/TOF/src/DataFormatsTOFLinkDef.h b/DataFormats/Detectors/TOF/src/DataFormatsTOFLinkDef.h index 55d1fd3973e70..03004e4c22afa 100644 --- a/DataFormats/Detectors/TOF/src/DataFormatsTOFLinkDef.h +++ b/DataFormats/Detectors/TOF/src/DataFormatsTOFLinkDef.h @@ -33,8 +33,6 @@ #pragma link C++ class std::vector < o2::dataformats::CalibInfoTOFshort> + ; #pragma link C++ class std::vector < o2::dataformats::CalibInfoTOF> + ; -#pragma link C++ class o2::tof::Parameters < 5> + ; -#pragma link C++ class o2::tof::ParameterCollection + ; #pragma link C++ class o2::tof::CTFHeader + ; #pragma link C++ class o2::tof::CompressedInfos + ; diff --git a/DataFormats/Detectors/TOF/src/ParameterContainers.cxx b/DataFormats/Detectors/TOF/src/ParameterContainers.cxx new file mode 100644 index 0000000000000..91f723873e9cd --- /dev/null +++ b/DataFormats/Detectors/TOF/src/ParameterContainers.cxx @@ -0,0 +1,62 @@ +// 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 ParameterContainers.h +/// \author Francesco Noferini +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// @since 2022-11-08 +/// \brief Implementation of the containers for the general parameters + +#include "DataFormatsTOF/ParameterContainers.h" + +// ClassImp(o2::tof::Parameters); +using namespace o2::tof; + +bool ParameterCollection::addParameter(const std::string& pass, const std::string& parName, float value) +{ + const bool alreadyPresent = hasKey(pass); + if (alreadyPresent) { + LOG(debug) << "Changing parametrization corresponding to key " << pass << " from size " << mParameters[pass].size() << " to " << parName; + } else { + mParameters[pass] = std::unordered_map{}; + LOG(debug) << "Adding new parametrization corresponding to key " << pass << ": " << parName; + } + mParameters[pass][parName] = value; + return true; +} + +int ParameterCollection::getSize(const std::string& pass) const +{ + if (!hasKey(pass)) { + return -1; + } + return mParameters.at(pass).size(); +} + +void ParameterCollection::print() const +{ + for (const auto& [pass, pars] : mParameters) { + print(pass); + } +} + +void ParameterCollection::print(const std::string& pass) const +{ + const auto& size = getSize(pass); + if (size < 0) { + LOG(info) << "empty pass: " << pass; + return; + } + LOG(info) << "Pass \"" << pass << "\" with size " << size; + for (const auto& [par, value] : mParameters.at(pass)) { + LOG(info) << "par name = " << par << ", value = " << value; + } +} diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/BetheBlochAleph.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/BetheBlochAleph.h index e8fe7457f3091..28b224298f36f 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/BetheBlochAleph.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/BetheBlochAleph.h @@ -12,27 +12,17 @@ #ifndef AliceO2_TPC_BETHEBLOCH_H_ #define AliceO2_TPC_BETHEBLOCH_H_ -#include "GPUCommonDef.h" -#include "GPUCommonMath.h" +#include "MathUtils/BetheBlochAleph.h" -namespace o2 -{ -namespace tpc +namespace o2::tpc { template GPUdi() T BetheBlochAleph(T bg, T kp1, T kp2, T kp3, T kp4, T kp5) { - T beta = bg / o2::gpu::GPUCommonMath::Sqrt(static_cast(1.) + bg * bg); - - T aa = o2::gpu::GPUCommonMath::Pow(beta, kp4); - T bb = o2::gpu::GPUCommonMath::Pow(static_cast(1.) / bg, kp5); - bb = o2::gpu::GPUCommonMath::Log(kp3 + bb); - - return (kp2 - aa - bb) * kp1 / aa; + return o2::common::BetheBlochAleph(bg, kp1, kp2, kp3, kp4, kp5); } -} // namespace tpc -} // namespace o2 +} // namespace o2::tpc #endif 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 1d7b10dc965f7..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; @@ -115,6 +113,9 @@ class CalibdEdxCorrection /// Single fit parameters averaged over all sectors for a stack type float getMeanEntries(const GEMstack stack, ChargeType charge) const; + /// set all corrections to 1, used for default initialization and to reset corrections + void setUnity(); + #endif private: 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/include/DataFormatsTPC/Defs.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h index 9b8853a10535d..fa04586479a22 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/Defs.h @@ -97,23 +97,6 @@ enum class StatisticsType { MeanStdDev ///< Use mean and standard deviation }; -enum class PadFlags : unsigned short { - flagGoodPad = 1 << 0, ///< flag for a good pad binary 0001 - flagDeadPad = 1 << 1, ///< flag for a dead pad binary 0010 - flagUnknownPad = 1 << 2, ///< flag for unknown status binary 0100 - flagSaturatedPad = 1 << 3, ///< flag for saturated status binary 0100 - flagHighPad = 1 << 4, ///< flag for pad with extremly high IDC value - flagLowPad = 1 << 5, ///< flag for pad with extremly low IDC value - flagSkip = 1 << 6, ///< flag for defining a pad which is just ignored during the calculation of I1 and IDCDelta - flagFEC = 1 << 7, ///< flag for a whole masked FEC - flagNeighbour = 1 << 8, ///< flag if n neighbouring pads are outlier - flagAllNoneGood = flagDeadPad | flagUnknownPad | flagSaturatedPad | flagHighPad | flagLowPad | flagSkip | flagFEC | flagNeighbour, -}; - -inline PadFlags operator&(PadFlags a, PadFlags b) { return static_cast(static_cast(a) & static_cast(b)); } -inline PadFlags operator~(PadFlags a) { return static_cast(~static_cast(a)); } -inline PadFlags operator|(PadFlags a, PadFlags b) { return static_cast(static_cast(a) | static_cast(b)); } - // default point definitions for PointND, PointNDlocal, PointNDglobal are in // MathUtils/CartesianND.h diff --git a/DataFormats/Detectors/TPC/include/DataFormatsTPC/RawDataTypes.h b/DataFormats/Detectors/TPC/include/DataFormatsTPC/RawDataTypes.h index 26d3fe9cf21cc..db96280bde534 100644 --- a/DataFormats/Detectors/TPC/include/DataFormatsTPC/RawDataTypes.h +++ b/DataFormats/Detectors/TPC/include/DataFormatsTPC/RawDataTypes.h @@ -28,6 +28,7 @@ enum Type : char { ZS = 2, ///< final Zero Suppression (can be ILBZS, DLBZS) IDC = 3, ///< Integrated Digitial Currents, with priority bit to end up in separate buffer SAC = 4, ///< Sampled Analogue Currents from the current monitor + CMV = 5, ///< Common mode values }; const std::unordered_map TypeNameMap{ @@ -36,6 +37,7 @@ const std::unordered_map TypeNameMap{ {Type::ZS, "ZS"}, {Type::IDC, "IDC"}, {Type::SAC, "SAC"}, + {Type::CMV, "CMV"}, }; } // namespace o2::tpc::raw_data_types diff --git a/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx b/DataFormats/Detectors/TPC/src/CalibdEdxCorrection.cxx index 0991c8693d8e8..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")); @@ -168,3 +172,18 @@ float CalibdEdxCorrection::getMeanEntries(const GEMstack stack, ChargeType charg return mean / (SECTORSPERSIDE * SIDES); } + +void CalibdEdxCorrection::setUnity() +{ + for (int i = 0; i < FitSize; ++i) { + for (int j = 0; j < ParamSize; ++j) { + mParams[i][j] = 0.f; + } + mParams[i][0] = 1.f; // constant term = 1 + mChi2[i] = 0.f; + mEntries[i] = 0; + } + mDims = 0; +} + +#endif // GPUCA_STANDALONE diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalGain.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalGain.h index f90101e7a4f21..97b3a73a86c03 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalGain.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalGain.h @@ -33,12 +33,52 @@ class CalGain void setMPVdEdx(int iDet, float mpv) { mMPVdEdx[iDet] = mpv; } - float getMPVdEdx(int iDet) const { return mMPVdEdx[iDet]; } + float getMPVdEdx(int iDet, bool defaultAvg = true) const + { + // if defaultAvg = false, we take the value stored whatever it is + // if defaultAvg = true and we have default value or bad value stored, we take the average on all chambers instead + if (!defaultAvg || isGoodGain(iDet)) + return mMPVdEdx[iDet]; + else { + if (std::fabs(mMeanGain + 999.) < 1e-6) + mMeanGain = getAverageGain(); + return mMeanGain; + } + } + + float getAverageGain() const + { + float averageGain = 0.; + int ngood = 0; + + for (int iDet = 0; iDet < constants::MAXCHAMBER; iDet++) { + if (isGoodGain(iDet)) { + // The chamber has correct calibration + ngood++; + averageGain += mMPVdEdx[iDet]; + } + } + if (ngood == 0) { + // we should make sure it never happens + return constants::MPVDEDXDEFAULT; + } + averageGain /= ngood; + return averageGain; + } + + bool isGoodGain(int iDet) const + { + if (std::fabs(mMPVdEdx[iDet] - constants::MPVDEDXDEFAULT) > 1e-6) + return true; + else + return false; + } private: std::array mMPVdEdx{}; ///< Most probable value of dEdx distribution per TRD chamber + mutable float mMeanGain{-999.}; ///! average gain, calculated only once - ClassDefNV(CalGain, 1); + ClassDefNV(CalGain, 2); }; } // namespace trd diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalVdriftExB.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalVdriftExB.h index bad9dcfef4e37..280f9d0d4b8a9 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalVdriftExB.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/CalVdriftExB.h @@ -34,14 +34,102 @@ class CalVdriftExB void setVdrift(int iDet, float vd) { mVdrift[iDet] = vd; } void setExB(int iDet, float exb) { mExB[iDet] = exb; } - float getVdrift(int iDet) const { return mVdrift[iDet]; } - float getExB(int iDet) const { return mExB[iDet]; } + float getVdrift(int iDet, bool defaultAvg = true) const + { + // if defaultAvg = false, we take the value stored whatever it is + // if defaultAvg = true and we have default value or bad value stored, we take the average on all chambers instead + if (!defaultAvg || (isGoodExB(iDet) && isGoodVdrift(iDet))) + return mVdrift[iDet]; + else { + if (std::fabs(mMeanVdrift + 999.) < 1e-6) + mMeanVdrift = getAverageVdrift(); + return mMeanVdrift; + } + } + float getExB(int iDet, bool defaultAvg = true) const + { + if (!defaultAvg || (isGoodExB(iDet) && isGoodVdrift(iDet))) + return mExB[iDet]; + else { + if (std::fabs(mMeanExB + 999.) < 1e-6) + mMeanExB = getAverageExB(); + return mMeanExB; + } + } + + float getAverageVdrift() const + { + float averageVdrift = 0.; + int ngood = 0; + + for (int iDet = 0; iDet < constants::MAXCHAMBER; iDet++) { + if (isGoodExB(iDet) && isGoodVdrift(iDet)) { + // Both values need to be correct to declare a chamber as well calibrated + ngood++; + averageVdrift += mVdrift[iDet]; + } + } + if (ngood == 0) { + // we should make sure it never happens + return constants::VDRIFTDEFAULT; + } + averageVdrift /= ngood; + return averageVdrift; + } + + float getAverageExB() const + { + float averageExB = 0.; + int ngood = 0; + + for (int iDet = 0; iDet < constants::MAXCHAMBER; iDet++) { + if (isGoodExB(iDet) && isGoodVdrift(iDet)) { + // Both values need to be correct to declare a chamber as well calibrated + ngood++; + averageExB += mExB[iDet]; + } + } + if (ngood == 0) { + // we should make sure it never happens + return constants::EXBDEFAULT; + } + averageExB /= ngood; + return averageExB; + } + + bool isGoodExB(int iDet) const + { + // check if value is well calibrated or not + // default calibration if not enough entries + // close to boundaries indicate a failed fit + if (std::fabs(mExB[iDet] - constants::EXBDEFAULT) > 1e-6 && + std::fabs(mExB[iDet] - constants::EXBMIN) > 0.01 && + std::fabs(mExB[iDet] - constants::EXBMAX) > 0.01) + return true; + else + return false; + } + + bool isGoodVdrift(int iDet) const + { + // check if value is well calibrated or not + // default calibration if not enough entries + // close to boundaries indicate a failed fit + if (std::fabs(mVdrift[iDet] - constants::VDRIFTDEFAULT) > 1e-6 && + std::fabs(mVdrift[iDet] - constants::VDRIFTMIN) > 0.1 && + std::fabs(mVdrift[iDet] - constants::VDRIFTMAX) > 0.1) + return true; + else + return false; + } private: std::array mVdrift{}; ///< calibrated drift velocity per TRD chamber std::array mExB{}; ///< calibrated Lorentz angle per TRD chamber + mutable float mMeanVdrift{-999.}; ///! average drift velocity, calculated only once + mutable float mMeanExB{-999.}; ///! average lorentz angle, calculated only once - ClassDefNV(CalVdriftExB, 1); + ClassDefNV(CalVdriftExB, 2); }; } // namespace trd diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/Constants.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/Constants.h index 7a650cf3699cf..9a4da1024e251 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/Constants.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/Constants.h @@ -75,7 +75,11 @@ constexpr int TIMEBINS = 30; ///< the number of time bins constexpr float MAXIMPACTANGLE = 25.f; ///< the maximum impact angle for tracks relative to the TRD detector plane to be considered for vDrift and ExB calibration constexpr int NBINSANGLEDIFF = 25; ///< the number of bins for the track angle used for the vDrift and ExB calibration based on the tracking constexpr double VDRIFTDEFAULT = 1.546; ///< default value for vDrift +constexpr double VDRIFTMIN = 0.4; ///< min value for vDrift +constexpr double VDRIFTMAX = 2.0; ///< max value for vDrift constexpr double EXBDEFAULT = 0.0; ///< default value for LorentzAngle +constexpr double EXBMIN = -0.4; ///< min value for LorentzAngle +constexpr double EXBMAX = 0.4; ///< max value for LorentzAngle constexpr int NBINSGAINCALIB = 320; ///< number of bins in the charge (Q0+Q1+Q2) histogram for gain calibration constexpr float MPVDEDXDEFAULT = 42.; ///< default Most Probable Value of TRD dEdx constexpr float T0DEFAULT = 1.2; ///< default value for t0 diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/Digit.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/Digit.h index 28ec6c76f4bef..9eba0318a5a13 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/Digit.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/Digit.h @@ -59,7 +59,7 @@ class Digit Digit(int det, int row, int pad, ArrayADC adc, int phase = 0); Digit(int det, int row, int pad); // add adc data and pretrigger phase in a separate step Digit(int det, int rob, int mcm, int channel, ArrayADC adc, int phase = 0); - Digit(int det, int rob, int mcm, int channel); // add adc data in a seperate step + Digit(int det, int rob, int mcm, int channel, int phase = 0); // add adc data // Copy Digit(const Digit&) = default; @@ -74,9 +74,11 @@ class Digit void setDetector(int det) { mDetector = ((mDetector & 0xf000) | (det & 0xfff)); } void setADC(ArrayADC const& adc) { mADC = adc; } void setADC(const gsl::span& adc) { std::copy(adc.begin(), adc.end(), mADC.begin()); } - void setPreTrigPhase(int phase) { mDetector = (((phase & 0xf) << 12) | (mDetector & 0xfff)); } + // set the trigger phase make sure it is mapped to 2 bits as it can only have 4 valid numbers shifted 0,3,6,9 or 1,4,7,10 etc. + void setPreTrigPhase(int phase); // Get methods int getDetector() const { return mDetector & 0xfff; } + int getDetectorInFull() const { return mDetector; } // return the entire mDetector 16 bits, so far only for CTF encoding. int getHCId() const { return (mDetector & 0xfff) * 2 + (mROB % 2); } int getPadRow() const { return HelperMethods::getPadRowFromMCM(mROB, mMCM); } int getPadCol() const { return HelperMethods::getPadColFromADC(mROB, mMCM, mChannel); } diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/PHData.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/PHData.h index b8873a5247d03..fc46ca0207993 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/PHData.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/PHData.h @@ -61,6 +61,66 @@ class PHData ClassDefNV(PHData, 1); }; + +/* + This data type is used to send around the information required to fill PH plots per chamber + + |19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00| + ------------------------------------------------------------- + |type |nNeighb | time bin | detector number | + ------------------------------------------------------------- +*/ +/* + This data type is used to send around the information required to fill PH plots per chamber + + |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00| + ------------------------------------------------ + | ADC sum for all neigbours | + ------------------------------------------------ +*/ + +class PHDataHD +{ + public: + enum Origin : uint8_t { + ITSTPCTRD, + TPCTRD, + TRACKLET, + OTHER + }; + + PHDataHD() = default; + PHDataHD(int adc, int det, int tb, int nb, int type) { set(adc, det, tb, nb, type); } + + void set(int adc, int det, int tb, int nb, int type) + { + mDetector = det; + mTimeBin = tb; + mType = type; + mNNeighbours = nb; + mADC = adc; + } + + // the ADC sum for given time bin for up to three neighbours + int getADC() const { return mADC; } + // the TRD detector number + int getDetector() const { return mDetector; } + // the given time bin + int getTimebin() const { return mTimeBin; } + // number of neighbouring digits for which the ADC is accumulated + int getNNeighbours() const { return mNNeighbours; } + // the origin of this point: digit on ITS-TPC-TRD track, ... (see enum Origin above) + int getType() const { return mType; } + + private: + uint16_t mDetector{0}; + uint8_t mTimeBin{0}; + uint8_t mType{0}; + uint8_t mNNeighbours{0}; + uint16_t mADC{0}; + + ClassDefNV(PHDataHD, 1); +}; } // namespace o2::trd #endif // ALICEO2_TRD_PHDATA_H_ diff --git a/DataFormats/Detectors/TRD/include/DataFormatsTRD/RecoInputContainer.h b/DataFormats/Detectors/TRD/include/DataFormatsTRD/RecoInputContainer.h index 353f635306e68..032dd4162a785 100644 --- a/DataFormats/Detectors/TRD/include/DataFormatsTRD/RecoInputContainer.h +++ b/DataFormats/Detectors/TRD/include/DataFormatsTRD/RecoInputContainer.h @@ -27,7 +27,7 @@ #include "Framework/InputRecord.h" #include "SimulationDataFormat/MCTruthContainer.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include #include 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/DataFormatsTRDLinkDef.h b/DataFormats/Detectors/TRD/src/DataFormatsTRDLinkDef.h index 250a33b2c98e2..c6d36a7aee495 100644 --- a/DataFormats/Detectors/TRD/src/DataFormatsTRDLinkDef.h +++ b/DataFormats/Detectors/TRD/src/DataFormatsTRDLinkDef.h @@ -43,6 +43,7 @@ #pragma link C++ class o2::trd::ChannelInfo + ; #pragma link C++ class o2::trd::ChannelInfoContainer + ; #pragma link C++ struct o2::trd::PHData + ; +#pragma link C++ struct o2::trd::PHDataHD + ; #pragma link C++ class o2::trd::TRDDataCountersPerTimeFrame + ; #pragma link C++ class o2::trd::DataCountersPerTrigger + ; #pragma link C++ class std::vector < o2::trd::Tracklet64> + ; @@ -56,6 +57,7 @@ #pragma link C++ class std::vector < o2::trd::GainCalibHistos> + ; #pragma link C++ class std::vector < o2::trd::T0FitHistos> + ; #pragma link C++ class std::vector < o2::trd::PHData> + ; +#pragma link C++ class std::vector < o2::trd::PHDataHD> + ; #pragma link C++ class std::vector < o2::trd::KrCluster> + ; #pragma link C++ class std::vector < o2::trd::KrClusterTriggerRecord> + ; #pragma link C++ class std::vector < o2::trd::DataCountersPerTrigger> + ; diff --git a/DataFormats/Detectors/TRD/src/Digit.cxx b/DataFormats/Detectors/TRD/src/Digit.cxx index 9e94fe22068bb..37d6638ac0996 100644 --- a/DataFormats/Detectors/TRD/src/Digit.cxx +++ b/DataFormats/Detectors/TRD/src/Digit.cxx @@ -12,6 +12,7 @@ #include "DataFormatsTRD/Digit.h" #include #include +#include "fairlogger/Logger.h" namespace o2::trd { @@ -46,12 +47,18 @@ Digit::Digit(int det, int rob, int mcm, int channel, ArrayADC adc, int pretrigph setPreTrigPhase(pretrigphase); } -Digit::Digit(int det, int rob, int mcm, int channel) // add adc data in a seperate step +Digit::Digit(int det, int rob, int mcm, int channel, int pretrigphase) // add adc data in a seperate step { setDetector(det); setROB(rob); setMCM(mcm); setChannel(channel); + setPreTrigPhase(pretrigphase); +} + +void Digit::setPreTrigPhase(int phase) +{ + mDetector = ((((phase) & 0x3) << 12) | (mDetector & 0xfff)); } bool Digit::isSharedDigit() const 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/Upgrades/ALICE3/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt new file mode 100644 index 0000000000000..360b50d442d7d --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2019-2025 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(FD3) +add_subdirectory(TRK) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt new file mode 100644 index 0000000000000..e2219bb893612 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt @@ -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. + +o2_add_library(DataFormatsFD3 + SOURCES src/Hit.cxx + PUBLIC_LINK_LIBRARIES O2::FD3Base + O2::SimulationDataFormat + O2::CommonDataFormat + Microsoft.GSL::GSL + O2::DetectorsCommonDataFormats +) + +o2_target_root_dictionary(DataFormatsFD3 + HEADERS include/DataFormatsFD3/Hit.h +) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/FD3/include/DataFormatsFD3/Hit.h b/DataFormats/Detectors/Upgrades/ALICE3/FD3/include/DataFormatsFD3/Hit.h new file mode 100644 index 0000000000000..4fde2f6cde6b4 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/FD3/include/DataFormatsFD3/Hit.h @@ -0,0 +1,125 @@ +// 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 Hit.h +/// \brief Definition of the FD3 Hit class (based on ITSMFT and FV0) + +#ifndef ALICEO2_FVD_HIT_H_ +#define ALICEO2_FVD_HIT_H_ + +#include +#include "SimulationDataFormat/BaseHits.h" // for BasicXYZEHit +#include "Rtypes.h" // for Bool_t, Double_t, int, Double32_t, etc +#include "TVector3.h" // for TVector3 +#include "CommonUtils/ShmAllocator.h" + +namespace o2 +{ +namespace fd3 +{ + +class Hit : public o2::BasicXYZEHit +{ + public: + /// Default constructor + Hit() = default; + + /// Class Constructor + /// \param trackID Index of MCTrack + /// \param cellID Cell ID + /// \param startPos Coordinates at entrance to active volume [cm] + /// \param endPos Coordinates to active volume [cm] + /// \param startMom Momentum of track at entrance [GeV] + /// \param startE Energy of track at entrance [GeV] + /// \param endTime Final time [ns] + /// \param eLoss Energy deposit [GeV] + /// \param particlePdg PDG code of the partcile associated with the track + inline Hit(int trackID, + int cellID, + const math_utils::Point3D& startPos, + const math_utils::Point3D& endPos, + const math_utils::Vector3D& startMom, + double startE, + double endTime, + double eLoss, + int particlePdg); + + // Entrance position getters + math_utils::Point3D const& GetPosStart() const { return mPositionStart; } + float GetStartX() const { return mPositionStart.X(); } + float GetStartY() const { return mPositionStart.Y(); } + float GetStartZ() const { return mPositionStart.Z(); } + template + void GetStartPosition(F& x, F& y, F& z) const + { + x = GetStartX(); + y = GetStartY(); + z = GetStartZ(); + } + + // Momentum getters + math_utils::Vector3D const& GetMomentum() const { return mMomentumStart; } + math_utils::Vector3D& GetMomentum() { return mMomentumStart; } + float GetPx() const { return mMomentumStart.X(); } + float GetPy() const { return mMomentumStart.Y(); } + float GetPz() const { return mMomentumStart.Z(); } + float GetE() const { return mEnergyStart; } + float GetTotalEnergyAtEntrance() const { return GetE(); } + int GetParticlePdg() const { return mParticlePdg; } + + void Print(const Option_t* opt) const; + + private: + math_utils::Vector3D mMomentumStart; ///< momentum at entrance + math_utils::Point3D mPositionStart; ///< position at entrance (base mPos give position on exit) + float mEnergyStart; ///< total energy at entrance + int mParticlePdg; ///< PDG code of the particle associated with this track + + ClassDefNV(Hit, 1); +}; + +Hit::Hit(int trackID, + int detID, + const math_utils::Point3D& startPos, + const math_utils::Point3D& endPos, + const math_utils::Vector3D& startMom, + double startE, + double endTime, + double eLoss, + int particlePdg) + : BasicXYZEHit(endPos.X(), + endPos.Y(), + endPos.Z(), + endTime, + eLoss, + trackID, + detID), + mMomentumStart(startMom.X(), startMom.Y(), startMom.Z()), + mPositionStart(startPos.X(), startPos.Y(), startPos.Z()), + mEnergyStart(startE), + mParticlePdg(particlePdg) +{ +} + +} // namespace fd3 +} // namespace o2 + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; + +} // namespace std +#endif /* USESHM */ +#endif /* ALICEO2_FD3_HIT_H_ */ diff --git a/Detectors/FIT/FT0/workflow/src/FT0DataReaderDPLSpec.cxx b/DataFormats/Detectors/Upgrades/ALICE3/FD3/src/DataFormatsFD3LinkDef.h similarity index 72% rename from Detectors/FIT/FT0/workflow/src/FT0DataReaderDPLSpec.cxx rename to DataFormats/Detectors/Upgrades/ALICE3/FD3/src/DataFormatsFD3LinkDef.h index caa642794b561..1014b3d8c704e 100644 --- a/Detectors/FIT/FT0/workflow/src/FT0DataReaderDPLSpec.cxx +++ b/DataFormats/Detectors/Upgrades/ALICE3/FD3/src/DataFormatsFD3LinkDef.h @@ -9,16 +9,13 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @file FT0DataReaderDPLSpec.cxx +#ifdef __CLING__ -#include "FT0Workflow/FT0DataReaderDPLSpec.h" +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -using namespace o2::framework; +#pragma link C++ class o2::fd3::Hit + ; +#pragma link C++ class vector < o2::fd3::Hit> + ; -namespace o2 -{ -namespace ft0 -{ - -} // namespace ft0 -} // namespace o2 +#endif diff --git a/Detectors/Vertexing/src/FwdDCAFitterN.cxx b/DataFormats/Detectors/Upgrades/ALICE3/FD3/src/Hit.cxx similarity index 54% rename from Detectors/Vertexing/src/FwdDCAFitterN.cxx rename to DataFormats/Detectors/Upgrades/ALICE3/FD3/src/Hit.cxx index f7176aa5039fd..403a3402bd30c 100644 --- a/Detectors/Vertexing/src/FwdDCAFitterN.cxx +++ b/DataFormats/Detectors/Upgrades/ALICE3/FD3/src/Hit.cxx @@ -9,25 +9,27 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file DCAFitterN.cxx -/// \brief Defintions for N-prongs secondary vertex fit -/// \author ruben.shahoyan@cern.ch, adapted from central barrel to fwd rapidities by Rita Sadek, rita.sadek@cern.ch +/// \file Hit.cxx +/// \brief Implementation of the Hit class -#include "DetectorsVertexing/FwdDCAFitterN.h" +#include "DataFormatsFD3/Hit.h" +#include + +ClassImp(o2::fd3::Hit); namespace o2 { -namespace vertexing +namespace fd3 { -void __test_instance__() +void Hit::Print(const Option_t* opt) const { - FwdDCAFitter2 ft2; - FwdDCAFitter3 ft3; - o2::track::TrackParCovFwd tr; - ft2.process(tr, tr); - ft3.process(tr, tr, tr); + printf( + "Det: %5d Track: %6d E.loss: %.3e P: %+.3e %+.3e %+.3e\n" + "PosIn: %+.3e %+.3e %+.3e PosOut: %+.3e %+.3e %+.3e\n", + GetDetectorID(), GetTrackID(), GetEnergyLoss(), GetPx(), GetPy(), GetPz(), + GetStartX(), GetStartY(), GetStartZ(), GetX(), GetY(), GetZ()); } -} // namespace vertexing +} // namespace fd3 } // namespace o2 diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt new file mode 100644 index 0000000000000..c239a2a36845d --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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. + +o2_add_library(DataFormatsTRK + SOURCES src/Cluster.cxx + src/ROFRecord.cxx + PUBLIC_LINK_LIBRARIES O2::CommonDataFormat + O2::DataFormatsITSMFT + O2::SimulationDataFormat +) + +o2_target_root_dictionary(DataFormatsTRK + HEADERS include/DataFormatsTRK/Cluster.h + include/DataFormatsTRK/ROFRecord.h + LINKDEF src/DataFormatsTRKLinkDef.h +) diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h b/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h new file mode 100644 index 0000000000000..ec68191b3c43f --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/Cluster.h @@ -0,0 +1,38 @@ +// 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 ALICEO2_DATAFORMATSTRK_CLUSTER_H +#define ALICEO2_DATAFORMATSTRK_CLUSTER_H + +#include +#include +#include + +namespace o2::trk +{ + +struct Cluster { + uint16_t chipID = 0; + uint16_t row = 0; + uint16_t col = 0; + uint16_t size = 1; + int16_t subDetID = -1; + int16_t layer = -1; + int16_t disk = -1; + + std::string asString() const; + + ClassDefNV(Cluster, 1); +}; + +} // namespace o2::trk + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h b/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h new file mode 100644 index 0000000000000..86ee31389fd5f --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/include/DataFormatsTRK/ROFRecord.h @@ -0,0 +1,75 @@ +// 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 ALICEO2_DATAFORMATSTRK_ROFRECORD_H +#define ALICEO2_DATAFORMATSTRK_ROFRECORD_H + +#include "CommonDataFormat/InteractionRecord.h" +#include "CommonDataFormat/RangeReference.h" +#include +#include +#include + +namespace o2::trk +{ + +class ROFRecord +{ + public: + using EvIdx = o2::dataformats::RangeReference; + using BCData = o2::InteractionRecord; + using ROFtype = unsigned int; + + ROFRecord() = default; + ROFRecord(const BCData& bc, ROFtype rof, int idx, int n) + : mBCData(bc), mROFEntry(idx, n), mROFrame(rof) {} + + void setBCData(const BCData& bc) { mBCData = bc; } + void setROFrame(ROFtype rof) { mROFrame = rof; } + void setEntry(EvIdx entry) { mROFEntry = entry; } + void setFirstEntry(int idx) { mROFEntry.setFirstEntry(idx); } + void setNEntries(int n) { mROFEntry.setEntries(n); } + + const BCData& getBCData() const { return mBCData; } + BCData& getBCData() { return mBCData; } + EvIdx getEntry() const { return mROFEntry; } + EvIdx& getEntry() { return mROFEntry; } + int getNEntries() const { return mROFEntry.getEntries(); } + int getFirstEntry() const { return mROFEntry.getFirstEntry(); } + ROFtype getROFrame() const { return mROFrame; } + + std::string asString() const; + + private: + o2::InteractionRecord mBCData; + EvIdx mROFEntry; + ROFtype mROFrame = 0; + + ClassDefNV(ROFRecord, 1); +}; + +struct MC2ROFRecord { + using ROFtype = unsigned int; + + int eventRecordID = -1; + int rofRecordID = 0; + ROFtype minROF = 0; + ROFtype maxROF = 0; + + MC2ROFRecord() = default; + MC2ROFRecord(int evID, int rofRecID, ROFtype mnrof, ROFtype mxrof) : eventRecordID(evID), rofRecordID(rofRecID), minROF(mnrof), maxROF(mxrof) {} + + ClassDefNV(MC2ROFRecord, 1); +}; + +} // namespace o2::trk + +#endif diff --git a/Framework/Utils/test/DPLBroadcasterMerger.h b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx similarity index 52% rename from Framework/Utils/test/DPLBroadcasterMerger.h rename to DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.cxx index 4607d72a702b7..6c96692ea5a9e 100644 --- a/Framework/Utils/test/DPLBroadcasterMerger.h +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/Cluster.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,20 +9,20 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \author Gabriele Gaetano Fronzé, gfronze@cern.ch +#include "DataFormatsTRK/Cluster.h" +#include -#ifndef DPLBROADCASTERMERGER_H -#define DPLBROADCASTERMERGER_H +ClassImp(o2::trk::Cluster); -#include "Framework/WorkflowSpec.h" -#include "Framework/DataProcessorSpec.h" - -namespace o2 +namespace o2::trk { -namespace workflows + +std::string Cluster::asString() const { -o2::framework::WorkflowSpec DPLBroadcasterMergerWorkflow(); + std::ostringstream stream; + stream << "chip=" << chipID << " row=" << row << " col=" << col << " size=" << size + << " subDet=" << subDetID << " layer=" << layer << " disk=" << disk; + return stream.str(); } -} // namespace o2 -#endif // DPLBROADCASTERMERGER_H +} // namespace o2::trk diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h new file mode 100644 index 0000000000000..36528d9dd2c46 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/DataFormatsTRKLinkDef.h @@ -0,0 +1,25 @@ +// 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. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::trk::Cluster + ; +#pragma link C++ class std::vector < o2::trk::Cluster> + ; +#pragma link C++ class o2::trk::ROFRecord + ; +#pragma link C++ class std::vector < o2::trk::ROFRecord> + ; +#pragma link C++ class o2::trk::MC2ROFRecord + ; +#pragma link C++ class std::vector < o2::trk::MC2ROFRecord> + ; + +#endif diff --git a/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx new file mode 100644 index 0000000000000..79745f9854eb7 --- /dev/null +++ b/DataFormats/Detectors/Upgrades/ALICE3/TRK/src/ROFRecord.cxx @@ -0,0 +1,29 @@ +// 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 "DataFormatsTRK/ROFRecord.h" +#include + +ClassImp(o2::trk::ROFRecord); +ClassImp(o2::trk::MC2ROFRecord); + +namespace o2::trk +{ + +std::string ROFRecord::asString() const +{ + std::ostringstream stream; + stream << "IR=" << mBCData.asString() << " ROFrame=" << mROFrame + << " first=" << mROFEntry.getFirstEntry() << " n=" << mROFEntry.getEntries(); + return stream.str(); +} + +} // namespace o2::trk diff --git a/DataFormats/Detectors/Upgrades/CMakeLists.txt b/DataFormats/Detectors/Upgrades/CMakeLists.txt index a2d470b8ff6d5..0dfe07dc2827d 100644 --- a/DataFormats/Detectors/Upgrades/CMakeLists.txt +++ b/DataFormats/Detectors/Upgrades/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2019-2020 CERN and copyright holders of ALICE O2. +# Copyright 2019-2025 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,3 +10,4 @@ # or submit itself to any jurisdiction. message(STATUS "Building dataformats for upgrades") +add_subdirectory(ALICE3) 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 2dbfbd67d8d6c..dbcdb8e0bba89 100644 --- a/DataFormats/Headers/include/Headers/DataHeader.h +++ b/DataFormats/Headers/include/Headers/DataHeader.h @@ -588,6 +588,7 @@ constexpr o2::header::DataOrigin gDataOriginTF3{"TF3"}; constexpr o2::header::DataOrigin gDataOriginRCH{"RCH"}; constexpr o2::header::DataOrigin gDataOriginMI3{"MI3"}; constexpr o2::header::DataOrigin gDataOriginECL{"ECL"}; // upgrades +constexpr o2::header::DataOrigin gDataOriginFD3{"FD3"}; // upgrades constexpr o2::header::DataOrigin gDataOriginGPU{"GPU"}; @@ -599,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/Headers/include/Headers/DataHeaderHelpers.h b/DataFormats/Headers/include/Headers/DataHeaderHelpers.h index aa93414cfb99f..4f7e49acb4d98 100644 --- a/DataFormats/Headers/include/Headers/DataHeaderHelpers.h +++ b/DataFormats/Headers/include/Headers/DataHeaderHelpers.h @@ -79,7 +79,8 @@ struct fmt::formatter { fmt::format(" payloadSize : {}\n", (long long unsigned int)h.payloadSize) + fmt::format(" firstTForbit : {}\n", h.firstTForbit) + fmt::format(" tfCounter : {}\n", h.tfCounter) + - fmt::format(" runNumber : {}\n", h.runNumber); + fmt::format(" runNumber : {}\n", h.runNumber) + + fmt::format(" split : {}/{}\n", h.splitPayloadIndex, h.splitPayloadParts); return fmt::format_to(ctx.out(), "{}", res); } else { auto res = fmt::format("{}/{}/{}", diff --git a/DataFormats/Headers/include/Headers/SubframeMetadata.h b/DataFormats/Headers/include/Headers/SubframeMetadata.h deleted file mode 100644 index 255fa0ceb8db6..0000000000000 --- a/DataFormats/Headers/include/Headers/SubframeMetadata.h +++ /dev/null @@ -1,68 +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 SUBFRAMEMETADATA_H -#define SUBFRAMEMETADATA_H - -#include - -namespace o2 -{ -namespace data_flow -{ - -struct SubframeMetadata { - // TODO: replace with timestamp struct - // IDEA: not timeframeID because can be calculcated with helper function - // QUESTION: isn't the duration set to ~22ms? - uint64_t startTime = ~(uint64_t)0; - uint64_t duration = ~(uint64_t)0; - - //further meta data to be added - - // putting data specific to FLP origin - int flpIndex; -}; - -// Helper function to derive the timeframe id from the actual timestamp. -// Timestamp is in nanoseconds. Each Timeframe is ~22ms i.e. 2^17 nanoseconds, -// so we can get a unique id by dividing by the timeframe period and masking -// the lower 16 bits. Overlaps will only happen every ~ 22 minutes. -constexpr uint16_t - timeframeIdFromTimestamp(uint64_t timestamp, uint64_t timeFrameDuration) -{ - return (timestamp / timeFrameDuration) & 0xffff; -} - -// A Mockup class to describe some TPC-like payload -struct TPCTestCluster { - float x = 0.f; - float y = 0.f; - float z = 1.5f; - float q = 0.; - uint64_t timeStamp; // the time this thing was digitized/recorded -}; - -struct TPCTestPayload { - std::vector clusters; -}; - -// a mockup class to describe some "ITS" payload -struct ITSRawData { - float x = -1.; - float y = 1.; - uint64_t timeStamp; -}; - -} // namespace data_flow -} // namespace o2 - -#endif diff --git a/DataFormats/Legacy/HLT/include/AliceHLT/TPCRawCluster.h b/DataFormats/Legacy/HLT/include/AliceHLT/TPCRawCluster.h deleted file mode 100644 index 59be2a86c3c2b..0000000000000 --- a/DataFormats/Legacy/HLT/include/AliceHLT/TPCRawCluster.h +++ /dev/null @@ -1,232 +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. - -//-*- Mode: C++ -*- - -#ifndef TPCRAWCLUSTER_H -#define TPCRAWCLUSTER_H -//**************************************************************************** -//* This file is free software: you can redistribute it and/or modify * -//* it under the terms of the GNU General Public License as published by * -//* the Free Software Foundation, either version 3 of the License, or * -//* (at your option) any later version. * -//* * -//* Primary Authors: Matthias Richter * -//* * -//* The authors make no claims about the suitability of this software for * -//* any purpose. It is provided "as is" without express or implied warranty. * -//**************************************************************************** - -// @file TPCRawCluster.h -// @author Matthias Richter -// @since 2015-09-27 -// @brief ALICE HLT TPC raw cluster structure and tools - -#include -#include // ifstream -#include // memcpy - -namespace o2 -{ -namespace AliceHLT -{ - -/** - * @struct RawCluster - * This is a redefinition from AliRoot/HLT/TPCLib/AliHLTTPCRawCluster.h for the - * sake of reading HLT TPC raw cluster files into O2. - * - * TODO: there is no dependence on AliRoot, however, a test needs to be added - * to check consistency if AliRoot is available in the build. - */ -struct RawCluster { - - int16_t GetPadRow() const { return fPadRow; } - float GetPad() const { return fPad; } - float GetTime() const { return fTime; } - float GetSigmaPad2() const { return fSigmaPad2; } - float GetSigmaTime2() const { return fSigmaTime2; } - int32_t GetCharge() const { return fCharge; } - int32_t GetQMax() const { return fQMax; } - bool GetFlagSplitPad() const { return (fFlags & (1 << 0)); } - bool GetFlagSplitTime() const { return (fFlags & (1 << 1)); } - bool GetFlagSplitAny() const { return (fFlags & 3); } - uint16_t GetFlags() const { return (fFlags); } - - int16_t fPadRow; - uint16_t fFlags; //Flags: (1 << 0): Split in pad direction - // (1 << 1): Split in time direction - //During cluster merging, flags are or'd - float fPad; - float fTime; - float fSigmaPad2; - float fSigmaTime2; - uint16_t fCharge; - uint16_t fQMax; -}; - -/** - * @struct RawClusterData - * Header data struct for a raw cluster block - */ -struct RawClusterData { - uint32_t fVersion; // version number - uint32_t fCount; // number of clusters - RawCluster fClusters[0]; // array of clusters -}; - -std::ostream& operator<<(std::ostream& stream, const RawCluster& cluster) -{ - stream << "TPCRawCluster:" - << " " << cluster.GetPadRow() - << " " << cluster.GetPad() - << " " << cluster.GetTime() - << " " << cluster.GetSigmaPad2() - << " " << cluster.GetSigmaTime2() - << " " << cluster.GetCharge() - << " " << cluster.GetQMax(); - return stream; -} - -/** - * @class RawClusterArray Wrapper to binary data block of HLT TPC raw clusters - * Container class which provides access to the content of a binary block of - * HLT TPC raw clusters. - */ -class RawClusterArray -{ - public: - RawClusterArray() : mBuffer(nullptr), mBufferSize(0), mNClusters(0), mClusters(NULL), mClustersEnd(NULL) {} - RawClusterArray(const char* filename) : mBuffer(nullptr), mBufferSize(0), mNClusters(0), mClusters(NULL), mClustersEnd(NULL) - { - init(filename); - } - RawClusterArray(unsigned char* buffer, int size) : mBuffer(nullptr), mBufferSize(0), mNClusters(0), mClusters(NULL), mClustersEnd(NULL) - { - init(buffer, size); - } - ~RawClusterArray() {} - - typedef uint8_t Buffer_t; - - int init(const char* filename) - { - std::ifstream input(filename, std::ifstream::binary); - clear(0); - if (input) { - // get length of file: - input.seekg(0, input.end); - int length = input.tellg(); - input.seekg(0, input.beg); - - // allocate memory: - mBuffer = new Buffer_t[length]; - mBufferSize = length; - - // read data as a block: - input.read(reinterpret_cast(mBuffer), length); - if (!input.good()) { - clear(-1); - std::cerr << "failed to read " << length << " byte(s) from file " << filename << std::endl; - } - - input.close(); - return init(); - } - std::cerr << "failed to open file " << filename << std::endl; - return -1; - } - - int init(unsigned char* buffer, int size) - { - if (!buffer || size <= 0) - return -1; - clear(0); - mBuffer = new Buffer_t[size]; - mBufferSize = size; - memcpy(mBuffer, buffer, size); - return init(); - } - - int GetNClusters() const { return mNClusters; } - - RawCluster* begin() { return mClusters; } - - RawCluster* end() { return mClustersEnd; } - - RawCluster& operator[](int i) - { - if (i + 1 > mNClusters) { - // runtime exeption? - static RawCluster dummy; - return dummy; - } - return *(mClusters + i); - } - - void print() { print(std::cout); } - - template - StreamT& print(StreamT& stream) - { - std::cout << "RawClusterArray: " << mNClusters << " cluster(s)" << std::endl; - for (RawCluster* cluster = mClusters; cluster != mClustersEnd; cluster++) { - std::cout << " " << *cluster << std::endl; - } - return stream; - } - - private: - int init() - { - if (mBuffer == nullptr || mBufferSize == 0) - return 0; - if (mBufferSize < sizeof(RawClusterData)) - return -1; - RawClusterData& clusterData = *reinterpret_cast(mBuffer); - - if (clusterData.fCount * sizeof(RawCluster) + sizeof(RawClusterData) > mBufferSize) { - std::cerr << "Format error, " << clusterData.fCount << " cluster(s) " - << "would require " - << (clusterData.fCount * sizeof(RawCluster) + sizeof(RawClusterData)) - << " byte(s), but only " << mBufferSize << " available" << std::endl; - return clear(-1); - } - - mNClusters = clusterData.fCount; - mClusters = clusterData.fClusters; - mClustersEnd = mClusters + mNClusters; - - return mNClusters; - } - - int clear(int returnValue) - { - mNClusters = 0; - mClusters = NULL; - mClustersEnd = NULL; - delete[] mBuffer; - mBuffer = nullptr; - mBufferSize = 0; - - return returnValue; - } - - Buffer_t* mBuffer; - int mBufferSize; - int mNClusters; - RawCluster* mClusters; - RawCluster* mClustersEnd; -}; - -}; // namespace AliceHLT -}; // namespace o2 -#endif diff --git a/DataFormats/Parameters/include/DataFormatsParameters/AggregatedRunInfo.h b/DataFormats/Parameters/include/DataFormatsParameters/AggregatedRunInfo.h index bd2cb0c5cbb27..d0347114b5b4c 100644 --- a/DataFormats/Parameters/include/DataFormatsParameters/AggregatedRunInfo.h +++ b/DataFormats/Parameters/include/DataFormatsParameters/AggregatedRunInfo.h @@ -23,6 +23,7 @@ namespace o2::parameters { class GRPECSObject; +class GRPLHCIFData; /// Composite struct where one may collect important global properties of data "runs" /// aggregated from various sources (GRPECS, RunInformation CCDB entries, etc.). @@ -39,8 +40,9 @@ struct AggregatedRunInfo { // we may have pointers to actual data source objects GRPECS, ... const o2::parameters::GRPECSObject* grpECS = nullptr; // pointer to GRPECSobject (fetched during struct building) + const o2::parameters::GRPLHCIFData* grpLHC = nullptr; - static AggregatedRunInfo buildAggregatedRunInfo(int runnumber, long sorMS, long eorMS, long orbitResetMUS, const o2::parameters::GRPECSObject* grpecs, const std::vector* ctfFirstRunOrbitVec); + static AggregatedRunInfo buildAggregatedRunInfo(int runnumber, long sorMS, long eorMS, long orbitResetMUS, const o2::parameters::GRPECSObject* grpecs, const std::vector* ctfFirstRunOrbitVec, const o2::parameters::GRPLHCIFData* grplhcif = nullptr); // fills and returns AggregatedRunInfo for a given data run number. static AggregatedRunInfo buildAggregatedRunInfo_DATA(o2::ccdb::CCDBManagerInstance& ccdb, int runnumber); diff --git a/DataFormats/Parameters/src/AggregatedRunInfo.cxx b/DataFormats/Parameters/src/AggregatedRunInfo.cxx index 5495ae73bd6ca..40402a33af68b 100644 --- a/DataFormats/Parameters/src/AggregatedRunInfo.cxx +++ b/DataFormats/Parameters/src/AggregatedRunInfo.cxx @@ -15,6 +15,7 @@ #include "DataFormatsParameters/AggregatedRunInfo.h" #include "CCDB/BasicCCDBManager.h" #include "DataFormatsParameters/GRPECSObject.h" +#include "DataFormatsParameters/GRPLHCIFData.h" #include "CommonConstants/LHCConstants.h" #include "Framework/Logger.h" #include @@ -42,14 +43,15 @@ o2::parameters::AggregatedRunInfo AggregatedRunInfo::buildAggregatedRunInfo_DATA std::map metadata; metadata["runNumber"] = Form("%d", runnumber); auto grpecs = ccdb.getSpecific("GLO/Config/GRPECS", run_mid_timestamp, metadata); + auto grplhcif = ccdb.getSpecific("GLO/Config/GRPLHCIF", run_mid_timestamp); // no run metadata here bool oldFatalState = ccdb.getFatalWhenNull(); ccdb.setFatalWhenNull(false); auto ctp_first_run_orbit = ccdb.getForTimeStamp>("CTP/Calib/FirstRunOrbit", run_mid_timestamp); ccdb.setFatalWhenNull(oldFatalState); - return buildAggregatedRunInfo(runnumber, sor, eor, tsOrbitReset, grpecs, ctp_first_run_orbit); + return buildAggregatedRunInfo(runnumber, sor, eor, tsOrbitReset, grpecs, ctp_first_run_orbit, grplhcif); } -o2::parameters::AggregatedRunInfo AggregatedRunInfo::buildAggregatedRunInfo(int runnumber, long sorMS, long eorMS, long orbitResetMUS, const o2::parameters::GRPECSObject* grpecs, const std::vector* ctfFirstRunOrbitVec) +o2::parameters::AggregatedRunInfo AggregatedRunInfo::buildAggregatedRunInfo(int runnumber, long sorMS, long eorMS, long orbitResetMUS, const o2::parameters::GRPECSObject* grpecs, const std::vector* ctfFirstRunOrbitVec, const o2::parameters::GRPLHCIFData* grplhcif) { auto nOrbitsPerTF = grpecs->getNHBFPerTF(); // calculate SOR/EOR orbits @@ -81,7 +83,7 @@ o2::parameters::AggregatedRunInfo AggregatedRunInfo::buildAggregatedRunInfo(int orbitSOR = (orbitSOR / nOrbitsPerTF + 1) * nOrbitsPerTF; } } - return AggregatedRunInfo{runnumber, sorMS, eorMS, nOrbitsPerTF, orbitResetMUS, orbitSOR, orbitEOR, grpecs}; + return AggregatedRunInfo{runnumber, sorMS, eorMS, nOrbitsPerTF, orbitResetMUS, orbitSOR, orbitEOR, grpecs, grplhcif}; } namespace diff --git a/DataFormats/Parameters/src/GRPTool.cxx b/DataFormats/Parameters/src/GRPTool.cxx index 903d659940558..e7561e6fc1ef6 100644 --- a/DataFormats/Parameters/src/GRPTool.cxx +++ b/DataFormats/Parameters/src/GRPTool.cxx @@ -312,7 +312,7 @@ bool create_GRPs(Options const& opts) auto soreor = ccdbmgr.getRunDuration(opts.run); runStart = soreor.first; grp.setTimeStart(runStart); - grp.setTimeEnd(runStart + 3600000); + grp.setTimeEnd(soreor.second); grp.setNHBFPerTF(opts.orbitsPerTF); std::vector modules{}; if (!o2::conf::SimConfig::determineActiveModulesList(opts.detectorList, opts.readout, std::vector(), modules)) { diff --git a/DataFormats/Reconstruction/CMakeLists.txt b/DataFormats/Reconstruction/CMakeLists.txt index 86c0831d2134e..d3ca8fdc70ad6 100644 --- a/DataFormats/Reconstruction/CMakeLists.txt +++ b/DataFormats/Reconstruction/CMakeLists.txt @@ -8,6 +8,7 @@ # 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_compile_options(-O0 -g -fPIC) o2_add_library(ReconstructionDataFormats SOURCES src/TrackParametrization.cxx @@ -73,6 +74,7 @@ o2_target_root_dictionary( include/ReconstructionDataFormats/BCRange.h include/ReconstructionDataFormats/TrackHMP.h include/ReconstructionDataFormats/MatchInfoHMP.h + include/ReconstructionDataFormats/HelixHelper.h ) o2_add_test(Vertex diff --git a/Common/DCAFitter/include/DCAFitter/HelixHelper.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/HelixHelper.h similarity index 96% rename from Common/DCAFitter/include/DCAFitter/HelixHelper.h rename to DataFormats/Reconstruction/include/ReconstructionDataFormats/HelixHelper.h index bd710e459ac54..d197cba256c0e 100644 --- a/Common/DCAFitter/include/DCAFitter/HelixHelper.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/HelixHelper.h @@ -63,9 +63,10 @@ struct CrossInfo { { const auto& trcA = trax0.rC > trax1.rC ? trax0 : trax1; // designate the largest circle as A const auto& trcB = trax0.rC > trax1.rC ? trax1 : trax0; + nDCA = 0; float xDist = trcB.xC - trcA.xC, yDist = trcB.yC - trcA.yC; float dist2 = xDist * xDist + yDist * yDist, dist = o2::gpu::GPUCommonMath::Sqrt(dist2), rsum = trcA.rC + trcB.rC; - if (o2::gpu::GPUCommonMath::Sqrt(dist) < 1e-12) { + if (dist < 1e-12) { return nDCA; // circles are concentric? } if (dist > rsum) { // circles don't touch, chose a point in between @@ -75,9 +76,13 @@ struct CrossInfo { return nDCA; } notTouchingXY(dist, xDist, yDist, trcA, trcB.rC, isCollinear); - } else if (dist + trcB.rC < trcA.rC) { // the small circle is nestled into large one w/o touching - // select the point of closest approach of 2 circles - notTouchingXY(dist, xDist, yDist, trcA, -trcB.rC, isCollinear); + } else if (auto dfr = dist + trcB.rC - trcA.rC; dfr < 0.) { // the small circle is nestled into large one w/o touching + if (dfr > -maxDistXY) { + // select the point of closest approach of 2 circles + notTouchingXY(dist, xDist, yDist, trcA, -trcB.rC, isCollinear); + } else { + return nDCA; + } } else { // 2 intersection points if (isCollinear) { /// collinear tracks, e.g. electrons from photon conversion @@ -89,7 +94,7 @@ struct CrossInfo { xDCA[0] = r2_r * trcA.xC + r1_r * trcB.xC; yDCA[0] = r2_r * trcA.yC + r1_r * trcB.yC; nDCA = 1; - } else if (o2::gpu::GPUCommonMath::Sqrt(xDist) < o2::gpu::GPUCommonMath::Sqrt(yDist)) { + } else if (o2::gpu::GPUCommonMath::Abs(xDist) < o2::gpu::GPUCommonMath::Abs(yDist)) { // to simplify calculations, we move to new frame x->x+Xc0, y->y+Yc0, so that // the 1st one is centered in origin float a = (trcA.rC * trcA.rC - trcB.rC * trcB.rC + dist2) / (2. * yDist), b = -xDist / yDist, ab = a * b, bb = b * b; @@ -167,7 +172,7 @@ struct CrossInfo { /// yL(t) = yL + t Ky; Ky = (sinAlp + cosAlp* snp/csp) /// zL(t) = zL + t Kz; Kz = tgl / csp /// Note that Kx^2 + Ky^2 + Kz^2 = (1+tgl^2) / csp^2 - + nDCA = 0; float dx = trax1.xC - trax0.xC; // for straight line TrackAuxPar stores lab coordinates at referene point!!! float dy = trax1.yC - trax0.yC; // float dz = tr1.getZ() - tr0.getZ(); 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/TrackFwd.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackFwd.h index 50ed36d466d25..fca117583027f 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackFwd.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackFwd.h @@ -31,6 +31,12 @@ using SMatrix55Sym = ROOT::Math::SMatrix; using SMatrix5 = ROOT::Math::SVector; +template +class TrackParametrization; // fwd declaration for conversion method + +template +class TrackParametrizationWithError; // fwd declaration for conversion method + class TrackParFwd { // Forward track parameterization, kinematics only. public: @@ -42,6 +48,9 @@ class TrackParFwd TrackParFwd(TrackParFwd&&) = delete; TrackParFwd& operator=(TrackParFwd&&) = delete; + template + void toBarrelTrackPar(TrackParametrization& t) const; + /// return Z coordinate (cm) Double_t getZ() const { return mZ; } /// set Z coordinate (cm) @@ -145,6 +154,9 @@ class TrackParCovFwd : public TrackParFwd TrackParCovFwd& operator=(const TrackParCovFwd& tpf) = default; TrackParCovFwd(const Double_t z, const SMatrix5& parameters, const SMatrix55Sym& covariances, const Double_t chi2); + template + void toBarrelTrackParCov(TrackParametrizationWithError& t) const; + const SMatrix55Sym& getCovariances() const { return mCovariances; } void setCovariances(const SMatrix55Sym& covariances) { mCovariances = covariances; } void deleteCovariances() { mCovariances = SMatrix55Sym(); } diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrization.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrization.h index 9c3079208b2f5..918633d914230 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrization.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrization.h @@ -66,6 +66,9 @@ class DCA; namespace track { + +class TrackParFwd; // fwd declaration for conversion method + // aliases for track elements enum ParLabels : int { kY, kZ, @@ -119,6 +122,9 @@ constexpr float MaxPT = 100000.; // do not allow pTs exceeding constexpr float MinPTInv = 1. / MaxPT; // do not allow q/pTs less this value (to avoid NANs) constexpr float ELoss2EKinThreshInv = 1. / 0.025; // do not allow E.Loss correction step with dE/Ekin above the inverse of this value constexpr int MaxELossIter = 50; // max number of iteration for the ELoss to account for BB dependence on beta*gamma +constexpr float DefaultDCA = 999.f; // default DCA value +constexpr float DefaultDCACov = 999.f; // default DCA cov value + // uncomment this to enable correction for BB dependence on beta*gamma via BB derivative // #define _BB_NONCONST_CORR_ @@ -162,6 +168,8 @@ class TrackParametrization GPUd() value_t getTgl() const; GPUhd() value_t getQ2Pt() const; GPUd() value_t getCharge2Pt() const; + GPUd() value_t getR2() const; + GPUd() value_t getR() const; GPUd() int getAbsCharge() const; GPUd() PID getPID() const; GPUd() void setPID(const PID pid, bool passCharge = false); @@ -226,6 +234,7 @@ class TrackParametrization // parameters manipulation GPUd() bool correctForELoss(value_t xrho, bool anglecorr = false); GPUd() bool rotateParam(value_t alpha); + GPUd() bool rotateParam(value_t& alpha, value_t& ca, value_t& sa); GPUd() bool propagateParamTo(value_t xk, value_t b); GPUd() bool propagateParamTo(value_t xk, const dim3_t& b); GPUd() void invertParam(); @@ -246,6 +255,7 @@ class TrackParametrization GPUd() void printParam() const; GPUd() void printParamHexadecimal(); #ifndef GPUCA_ALIGPUCODE + void toFwdTrackPar(TrackParFwd& t) const; std::string asString() const; std::string asStringHexadecimal(); size_t hash() const { return hash(getX(), getAlpha(), getY(), getZ(), getSnp(), getTgl(), getQ2Pt()); } @@ -277,6 +287,7 @@ GPUdi() TrackParametrization::TrackParametrization(value_t x, value_t a : mX{x}, mAlpha{alpha}, mAbsCharge{char(gpu::CAMath::Abs(charge))}, mPID{pid} { // explicit constructor + math_utils::detail::bringToPMPi(mAlpha); for (int i = 0; i < kNParams; i++) { mP[i] = par[i]; } @@ -295,6 +306,7 @@ GPUdi() void TrackParametrization::set(value_t x, value_t alpha, const { mX = x; mAlpha = alpha; + math_utils::detail::bringToPMPi(mAlpha); mAbsCharge = char(gpu::CAMath::Abs(charge)); for (int i = 0; i < kNParams; i++) { mP[i] = par[i]; @@ -372,6 +384,20 @@ GPUdi() auto TrackParametrization::getCharge2Pt() const -> value_t return mAbsCharge ? mP[kQ2Pt] : 0.f; } +//____________________________________________________________ +template +GPUdi() auto TrackParametrization::getR2() const -> value_t +{ + return mX * mX + mP[kY] * mP[kY]; +} + +//____________________________________________________________ +template +GPUdi() auto TrackParametrization::getR() const -> value_t +{ + return gpu::CAMath::Sqrt(getR2()); +} + //____________________________________________________________ template GPUdi() int TrackParametrization::getAbsCharge() const @@ -430,6 +456,7 @@ template GPUdi() void TrackParametrization::setAlpha(value_t v) { mAlpha = v; + math_utils::detail::bringToPMPi(mAlpha); } //____________________________________________________________ diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h index cd9d1517a81b1..436dc42cff749 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/TrackParametrizationWithError.h @@ -25,6 +25,8 @@ namespace o2 namespace track { +class TrackParCovFwd; // fwd declaration for conversion method + template class TrackParametrizationWithError : public TrackParametrization { // track+error parameterization @@ -81,7 +83,8 @@ 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(); #endif @@ -89,9 +92,14 @@ class TrackParametrizationWithError : public TrackParametrization // parameters + covmat manipulation GPUd() bool testRotate(value_t alpha) const; GPUd() bool rotate(value_t alpha); - GPUd() bool propagateTo(value_t xk, value_t b); + GPUd() bool rotate(value_t alpha, TrackParametrization& linRef, value_t bz); + GPUd() bool propagateTo(value_t xk, value_t bz); + GPUd() bool propagateTo(value_t xk, TrackParametrization& linRef, value_t bz); + GPUd() bool propagateTo(value_t xk, value_t bz, TrackParametrization* linRef) { return linRef ? propagateTo(xk, *linRef, bz) : propagateTo(xk, bz); } GPUd() bool propagateTo(value_t xk, const dim3_t& b); - GPUd() bool propagateToDCA(const o2::dataformats::VertexBase& vtx, value_t b, o2::dataformats::DCA* dca = nullptr, value_t maxD = 999.f); + GPUd() bool propagateTo(value_t xk, TrackParametrization& linRef, const dim3_t& b); + GPUd() bool propagateTo(value_t xk, const dim3_t& b, TrackParametrization* linRef) { return linRef ? propagateTo(xk, *linRef, b) : propagateTo(xk, b); } + GPUd() bool propagateToDCA(const o2::dataformats::VertexBase& vtx, value_t bz, o2::dataformats::DCA* dca = nullptr, value_t maxD = 999.f); GPUd() void invert(); GPUd() value_t getPredictedChi2(const dim2_t& p, const dim3_t& cov) const; GPUd() value_t getPredictedChi2Quiet(const dim2_t& p, const dim3_t& cov) const; @@ -118,7 +126,7 @@ class TrackParametrizationWithError : public TrackParametrization GPUd() bool update(const BaseCluster& p); GPUd() bool correctForMaterial(value_t x2x0, value_t xrho, bool anglecorr = false); - + GPUd() bool correctForMaterial(TrackParametrization& linRef, value_t x2x0, value_t xrho, bool anglecorr = false); GPUd() void resetCovariance(value_t s2 = 0); GPUd() void checkCovariance(); GPUd() void checkCorrelations(); diff --git a/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h b/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h index 2d13e029f8c00..588a23d25a000 100644 --- a/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h +++ b/DataFormats/Reconstruction/include/ReconstructionDataFormats/Vertex.h @@ -45,9 +45,17 @@ class VertexBase static constexpr int kNCov = 6; GPUhdDefault() VertexBase() = default; GPUhdDefault() ~VertexBase() = default; - GPUhd() VertexBase(const math_utils::Point3D& pos, const std::array& cov) : mPos(pos), mCov(cov) + GPUhd() VertexBase(const float* pos, const float* cov) { + mPos = math_utils::Point3D(pos[0], pos[1], pos[2]); + mCov[kCovXX] = cov[kCovXX]; + mCov[kCovXY] = cov[kCovXY]; + mCov[kCovXZ] = cov[kCovXZ]; + mCov[kCovYY] = cov[kCovYY]; + mCov[kCovYZ] = cov[kCovYZ]; + mCov[kCovZZ] = cov[kCovZZ]; } + GPUhd() VertexBase(const math_utils::Point3D& pos, const std::array& cov) : mPos(pos), mCov(cov) {} #if !defined(GPUCA_NO_FMT) && !defined(GPUCA_GPUCODE_DEVICE) void print() const; @@ -58,6 +66,7 @@ class VertexBase GPUhd() float getX() const { return mPos.X(); } GPUhd() float getY() const { return mPos.Y(); } GPUhd() float getZ() const { return mPos.Z(); } + GPUhd() float getR() const { return gpu::CAMath::Hypot(mPos.X(), mPos.Y()); } GPUd() float getSigmaX2() const { return mCov[kCovXX]; } GPUd() float getSigmaY2() const { return mCov[kCovYY]; } GPUd() float getSigmaZ2() const { return mCov[kCovZZ]; } @@ -69,6 +78,7 @@ class VertexBase GPUd() float getSigmaZ() const { return gpu::CAMath::Sqrt(getSigmaZ2()); } GPUd() const std::array& getCov() const { return mCov; } + GPUd() float getCov(int e) const { return mCov[e]; } GPUd() math_utils::Point3D getXYZ() const { return mPos; } GPUd() math_utils::Point3D& getXYZ() { return mPos; } @@ -105,6 +115,7 @@ class VertexBase setSigmaYZ(syz); } GPUd() void setCov(const std::array& cov) { mCov = cov; } + GPUd() void setCov(float c, int e) { mCov[e] = c; } bool operator==(const VertexBase& other) const; bool operator!=(const VertexBase& other) const { return !(*this == other); } @@ -133,17 +144,18 @@ class Vertex : public VertexBase GPUhdDefault() Vertex() = default; GPUhdDefault() ~Vertex() = default; - GPUhd() Vertex(const math_utils::Point3D& pos, const std::array& cov, ushort nCont, float chi2) - : VertexBase(pos, cov), mChi2(chi2), mNContributors(nCont) - { - } + GPUhd() Vertex(const float* pos, const float* cov, ushort nCont, float chi2) : VertexBase(pos, cov), mChi2(chi2), mNContributors(nCont) {} + GPUhd() Vertex(const math_utils::Point3D& pos, const std::array& cov, ushort nCont, float chi2) : VertexBase(pos, cov), mChi2(chi2), mNContributors(nCont) {} #if !defined(GPUCA_NO_FMT) && !defined(GPUCA_GPUCODE_DEVICE) void print() const; 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++; } @@ -175,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 @@ -192,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/ReconstructionDataFormatsLinkDef.h b/DataFormats/Reconstruction/src/ReconstructionDataFormatsLinkDef.h index 6cd72e8668cc1..b386830d9872d 100644 --- a/DataFormats/Reconstruction/src/ReconstructionDataFormatsLinkDef.h +++ b/DataFormats/Reconstruction/src/ReconstructionDataFormatsLinkDef.h @@ -117,4 +117,7 @@ #pragma link C++ class o2::dataformats::StrangeTrack + ; #pragma link C++ class std::vector < o2::dataformats::StrangeTrack> + ; +#pragma link C++ class o2::track::TrackAuxPar + ; +#pragma link C++ class o2::track::CrossInfo + ; + #endif diff --git a/DataFormats/Reconstruction/src/TrackFwd.cxx b/DataFormats/Reconstruction/src/TrackFwd.cxx index dfe72c5b2ccc4..511ba122b2822 100644 --- a/DataFormats/Reconstruction/src/TrackFwd.cxx +++ b/DataFormats/Reconstruction/src/TrackFwd.cxx @@ -10,6 +10,8 @@ // or submit itself to any jurisdiction. #include "ReconstructionDataFormats/TrackFwd.h" +#include "ReconstructionDataFormats/TrackParametrization.h" +#include "ReconstructionDataFormats/TrackParametrizationWithError.h" #include "Math/MatrixFunctions.h" #include @@ -41,7 +43,7 @@ void TrackParFwd::propagateParamToZlinear(double zEnd) auto dZ = (zEnd - getZ()); auto phi0 = getPhi(); auto [sinphi0, cosphi0] = o2::math_utils::sincosd(phi0); - auto invtanl0 = 1.0 / getTanl(); + auto invtanl0 = 1.0 / getTgl(); auto n = dZ * invtanl0; mParameters(0) += n * cosphi0; mParameters(1) += n * sinphi0; @@ -572,5 +574,79 @@ void TrackParCovFwd::propagateToDCAhelix(double zField, const std::array +void TrackParFwd::toBarrelTrackPar(TrackParametrization& t) const +{ + // we select the barrel frame with alpha = phi, then by construction the snp is 0 + auto alpha = getPhi(); + auto csa = TMath::Cos(alpha), sna = TMath::Sin(alpha); + t.setAlpha(alpha); + t.setX(csa * getX() + sna * getY()); + t.setY(-sna * getX() + csa * getY()); + t.setZ(getZ()); + t.setSnp(0); + t.setTgl(getTanl()); + t.setQ2Pt(getInvQPt()); +} + +template +void TrackParCovFwd::toBarrelTrackParCov(TrackParametrizationWithError& t) const +{ + // We select the barrel frame with alpha = phi, then by construction the snp is 0 + auto alpha = getPhi(); + auto csa = TMath::Cos(alpha), sna = TMath::Sin(alpha); + t.setAlpha(alpha); + t.setX(csa * getX() + sna * getY()); + t.setY(-sna * getX() + csa * getY()); + t.setZ(getZ()); + t.setSnp(0); + t.setTgl(getTgl()); + t.setQ2Pt(getInvQPt()); + /* + The standard Jacobian d{barrel_param} / d{fwd_param} should be augmented by the "forward" uncertainty + in X_barrel translated to uncertainty in Z, i.e: + A fwd param variation delta_x, delta_y leads to barrel frame coordinate variaion + delta_xb = csa delta_x + sna delta_y + delta_yb = -sna delta_x + csa delta_y + with dx_b/dz = csp/tgL = 1/tgL, dy_b/dz = snp/tgL = 0 (for phi 0 in the tracking frame) the variation of delta_xb would require + a shift in delta_zb = -tgL delta_xb to stat at the same X. + So, for alpha=phi (-> snp=0) choice the full Jacobian fwd->barrel is: + / -sna csa 0 0 0 \ + | -tgL*csa -tgL*sna 0 0 0 | + | 0 0 1 0 0 | + | 0 0 0 1 0 | + \ 0 0 0 0 1 / + */ + const T a1 = -sna; + const T a2 = csa; + const T b1 = -getTgl() * csa; + const T b2 = -getTgl() * sna; + const T cphi = 1; + const auto& C = getCovariances(); + typename TrackParametrizationWithError::covMat_t covBarrel = { + T(a1 * a1 * C(0, 0) + 2 * a1 * a2 * C(0, 1) + a2 * a2 * C(1, 1)), // kSigY2 + T(a1 * b1 * C(0, 0) + (a1 * b2 + a2 * b1) * C(0, 1) + a2 * b2 * C(1, 1)), // kSigZY + T(b1 * b1 * C(0, 0) + 2 * b1 * b2 * C(0, 1) + b2 * b2 * C(1, 1)), // kSigZ2 + T(csa * (a1 * C(0, 2) + a2 * C(1, 2))), // kSigSnpY + T(csa * (b1 * C(0, 2) + b2 * C(1, 2))), // kSigSnpZ + T(csa * csa * C(2, 2)), // kSigSnp2 + T(a1 * C(0, 3) + a2 * C(1, 3)), // kSigTglY + T(b1 * C(0, 3) + b2 * C(1, 3)), // kSigTglZ + T(csa * C(2, 3)), // kSigTglSnp + T(C(3, 3)), // kSigTgl2 + T(a1 * C(0, 4) + a2 * C(1, 4)), // kSigQ2PtY + T(b1 * C(0, 4) + b2 * C(1, 4)), // kSigQ2PtZ + T(csa * C(2, 4)), // kSigQ2PtSnp + T(C(3, 4)), // kSigQ2PtTgl + T(C(4, 4)) // kSigQ2Pt2 + }; + t.setCov(covBarrel); +} + +template void TrackParFwd::toBarrelTrackPar(TrackParametrization&) const; +template void TrackParFwd::toBarrelTrackPar(TrackParametrization&) const; +template void TrackParCovFwd::toBarrelTrackParCov(TrackParametrizationWithError&) const; +template void TrackParCovFwd::toBarrelTrackParCov(TrackParametrizationWithError&) const; + } // namespace track } // namespace o2 diff --git a/DataFormats/Reconstruction/src/TrackParametrization.cxx b/DataFormats/Reconstruction/src/TrackParametrization.cxx index 1bdf9b55a60a0..c238b087d5086 100644 --- a/DataFormats/Reconstruction/src/TrackParametrization.cxx +++ b/DataFormats/Reconstruction/src/TrackParametrization.cxx @@ -26,6 +26,7 @@ #ifndef GPUCA_ALIGPUCODE #include +#include "ReconstructionDataFormats/TrackFwd.h" #endif using namespace o2::gpu; @@ -188,6 +189,38 @@ GPUd() bool TrackParametrization::rotateParam(value_t alpha) return true; } +//______________________________________________________________ +template +GPUd() bool TrackParametrization::rotateParam(value_t& alpha, value_t& ca, value_t& sa) +{ + // rotate to alpha frame + if (gpu::CAMath::Abs(getSnp()) > constants::math::Almost1) { + LOGP(debug, "Precondition is not satisfied: |sin(phi)|>1 ! {:f}", getSnp()); + return false; + } + // + math_utils::detail::bringToPMPi(alpha); + math_utils::detail::sincos(alpha - getAlpha(), sa, ca); + value_t snp = getSnp(), csp = gpu::CAMath::Sqrt((1.f - snp) * (1.f + snp)); // Improve precision + // RS: check if rotation does no invalidate track model (cos(local_phi)>=0, i.e. particle direction in local frame is along the X axis + if ((csp * ca + snp * sa) < 0) { + // LOGF(warning,"Rotation failed: local cos(phi) would become {:.2f}", csp * ca + snp * sa); + return false; + } + // + value_t tmp = snp * ca - csp * sa; + if (gpu::CAMath::Abs(tmp) > constants::math::Almost1) { + LOGP(debug, "Rotation failed: new snp {:.2f}", tmp); + return false; + } + value_t xold = getX(), yold = getY(); + mAlpha = alpha; + mX = xold * ca + yold * sa; + mP[kY] = -xold * sa + yold * ca; + mP[kSnp] = tmp; + return true; +} + //____________________________________________________________ template GPUd() bool TrackParametrization::propagateParamTo(value_t xk, const dim3_t& b) @@ -282,14 +315,14 @@ GPUd() bool TrackParametrization::propagateParamTo(value_t xk, const di // Do the final correcting step to the target plane (linear approximation) value_t x = vecLab[0], y = vecLab[1], z = vecLab[2]; - if (gpu::CAMath::Abs(dx) > constants::math::Almost0) { + if (gpu::CAMath::Abs(x - xk) > constants::math::Almost0) { if (gpu::CAMath::Abs(vecLab[3]) < constants::math::Almost0) { return false; } - dx = xk - vecLab[0]; - x += dx; - y += vecLab[4] / vecLab[3] * dx; - z += vecLab[5] / vecLab[3] * dx; + auto dxFin = xk - vecLab[0]; + x += dxFin; + y += vecLab[4] / vecLab[3] * dxFin; + z += vecLab[5] / vecLab[3] * dxFin; } // Calculate the track parameters @@ -378,6 +411,10 @@ GPUd() bool TrackParametrization::propagateParamToDCA(const math_utils: // Estimate the impact parameter neglecting the track curvature value_t d = gpu::CAMath::Abs(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + (*dca)[0] = o2::track::DefaultDCA; + (*dca)[1] = o2::track::DefaultDCA; + } return false; } value_t crv = getCurvature(b); @@ -399,6 +436,10 @@ GPUd() bool TrackParametrization::propagateParamToDCA(const math_utils: #else LOG(debug) << "failed to propagate to alpha=" << alp << " X=" << xv << " for vertex " << vtx.X() << ' ' << vtx.Y() << ' ' << vtx.Z(); #endif + if (dca) { // provide default DCA for failed propag + (*dca)[0] = o2::track::DefaultDCA; + (*dca)[1] = o2::track::DefaultDCA; + } return false; } *this = tmpT; @@ -575,7 +616,7 @@ template std::string TrackParametrization::asString() const { // print parameters as string - return fmt::format("X:{:+.4e} Alp:{:+.3e} Par: {:+.4e} {:+.4e} {:+.4e} {:+.4e} {:+.4e} |Q|:{:d} {:s}\n", + return fmt::format("X:{:+.4e} Alp:{:+.3e} Par: {:+.4e} {:+.4e} {:+.4e} {:+.4e} {:+.4e} |Q|:{:d} {:s}", getX(), getAlpha(), getY(), getZ(), getSnp(), getTgl(), getQ2Pt(), getAbsCharge(), getPID().getName()); } @@ -651,7 +692,7 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val // DirOutward (==1) - go along the track (increasing mX) // DirInward (==-1) - go backward (decreasing mX) // - const auto fy = mP[0], sn = mP[2]; + const double fy = mP[0], sn = mP[2]; const value_t kEps = 1.e-6; // if (gpu::CAMath::Abs(getSnp()) > constants::math::Almost1) { @@ -670,18 +711,18 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val if (r0 <= constants::math::Almost0) { return false; // the track is concentric to circle } - value_t tR2r0 = 1.f, g = 0.f, tmp = 0.f; + double tR2r0 = 1., g = 0., tmp = 0.; if (gpu::CAMath::Abs(circle.rC - r0) > kEps) { tR2r0 = circle.rC / r0; g = 0.5f * (r * r / (r0 * circle.rC) - tR2r0 - 1.f / tR2r0); tmp = 1.f + g * tR2r0; } else { tR2r0 = 1.0; - g = 0.5f * r * r / (r0 * circle.rC) - 1.f; - tmp = 0.5f * r * r / (r0 * r0); + g = 0.5 * r * r / (r0 * circle.rC) - 1.; + tmp = 0.5 * r * r / (r0 * r0); } - value_t det = (1.f - g) * (1.f + g); - if (det < 0.f) { + auto det = (1. - g) * (1. + g); + if (det < 0.) { return false; // does not reach raduis r } det = gpu::CAMath::Sqrt(det); @@ -691,25 +732,26 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val // where s0 and c0 make direction for the circle center (=circle.xC/r0 and circle.yC/r0) // x = circle.xC * tmp; - value_t y = circle.yC * tmp; + auto y = circle.yC * tmp; if (gpu::CAMath::Abs(circle.yC) > constants::math::Almost0) { // when circle.yC==0 the x,y is unique - value_t dfx = tR2r0 * gpu::CAMath::Abs(circle.yC) * det; - value_t dfy = tR2r0 * circle.xC * (circle.yC > 0.f ? det : -det); + auto dfx = tR2r0 * gpu::CAMath::Abs(circle.yC) * det; + auto dfy = tR2r0 * circle.xC * (circle.yC > 0. ? det : -det); if (dir == DirAuto) { // chose the one which corresponds to smallest step - value_t delta = (x - mX) * dfx - (y - fy) * dfy; // the choice of + in C will lead to smaller step if delta<0 - x += delta < 0.f ? dfx : -dfx; + auto delta = (x - mX) * dfx - (y - fy) * dfy; // the choice of + in C will lead to smaller step if delta<0 + x += delta < 0. ? dfx : -dfx; } else if (dir == DirOutward) { // along track direction: x must be > mX x -= dfx; // try the smallest step (dfx is positive) - value_t dfeps = mX - x; // handle special case of very small step + auto dfeps = mX - x; // handle special case of very small step if (dfeps < -kEps) { return true; } if (gpu::CAMath::Abs(dfeps) < kEps && gpu::CAMath::Abs(mX * mX + fy * fy - r * r) < kEps) { // are we already in right r? - return mX; + x = mX; + return true; } x += dfx + dfx; - value_t dxm = x - mX; - if (dxm > 0.f) { + auto dxm = x - mX; + if (dxm > 0.) { return true; } else if (dxm < -kEps) { return false; @@ -717,16 +759,17 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val x = mX; // don't move } else { // backward: x must be < mX x += dfx; // try the smallest step (dfx is positive) - value_t dfeps = x - mX; // handle special case of very small step + auto dfeps = x - mX; // handle special case of very small step if (dfeps < -kEps) { return true; } if (gpu::CAMath::Abs(dfeps) < kEps && gpu::CAMath::Abs(mX * mX + fy * fy - r * r) < kEps) { // are we already in right r? - return mX; + x = mX; + return true; } x -= dfx + dfx; - value_t dxm = x - mX; - if (dxm < 0.f) { + auto dxm = x - mX; + if (dxm < 0.) { return true; } if (dxm > kEps) { @@ -739,11 +782,11 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val return false; } } - return x; + return true; } // this is a straight track if (gpu::CAMath::Abs(sn) >= constants::math::Almost1) { // || to Y axis - value_t det = (r - mX) * (r + mX); + double det = (r - mX) * (r + mX); if (det < 0.f) { return false; // does not reach raduis r } @@ -753,7 +796,7 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val } det = gpu::CAMath::Sqrt(det); if (dir == DirOutward) { // along the track direction - if (sn > 0.f) { + if (sn > 0.) { if (fy > det) { return false; // track is along Y axis and above the circle } @@ -763,7 +806,7 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val } } } else if (dir == DirInward) { // against track direction - if (sn > 0.f) { + if (sn > 0.) { if (fy < -det) { return false; // track is along Y axis } @@ -772,13 +815,13 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val } } } else if (gpu::CAMath::Abs(sn) <= constants::math::Almost0) { // || to X axis - value_t det = (r - fy) * (r + fy); - if (det < 0.f) { + double det = (r - fy) * (r + fy); + if (det < 0.) { return false; // does not reach raduis r } det = gpu::CAMath::Sqrt(det); if (dir == DirAuto) { - x = mX > 0.f ? det : -det; // choose the solution requiring the smalest step + x = mX > 0. ? det : -det; // choose the solution requiring the smalest step return true; } else if (dir == DirOutward) { // along the track direction if (mX > det) { @@ -794,17 +837,17 @@ GPUd() bool TrackParametrization::getXatLabR(value_t r, value_t& x, val } } } else { // general case of straight line - value_t cs = gpu::CAMath::Sqrt((1.f - sn) * (1.f + sn)); - value_t xsyc = mX * sn - fy * cs; - value_t det = (r - xsyc) * (r + xsyc); - if (det < 0.f) { + auto cs = gpu::CAMath::Sqrt((1. - sn) * (1. + sn)); + auto xsyc = mX * sn - fy * cs; + auto det = (r - xsyc) * (r + xsyc); + if (det < 0.) { return false; // does not reach raduis r } det = gpu::CAMath::Sqrt(det); - value_t xcys = mX * cs + fy * sn; - value_t t = -xcys; + auto xcys = mX * cs + fy * sn; + auto t = -xcys; if (dir == DirAuto) { - t += t > 0.f ? -det : det; // chose the solution requiring the smalest step + t += t > 0. ? -det : det; // chose the solution requiring the smalest step } else if (dir > 0) { // go in increasing mX direction. ( t+-det > 0) if (t >= -det) { t += det; // take minimal step giving t>0 @@ -943,6 +986,21 @@ GPUd() typename TrackParametrization::value_t TrackParametrization +void TrackParametrization::toFwdTrackPar(TrackParFwd& t) const +{ + auto p = getXYZGlo(); + t.setZ(p.Z()); + t.setX(p.X()); + t.setY(p.Y()); + t.setPhi(getPhi()); + t.setTanl(getTgl()); + t.setInvQPt(getQ2Pt()); +} +#endif + namespace o2::track { #if !defined(GPUCA_GPUCODE) || defined(GPUCA_GPUCODE_DEVICE) // FIXME: DR: WORKAROUND to avoid CUDA bug creating host symbols for device code. diff --git a/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx b/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx index aee24238f1247..ee2e96736aa6d 100644 --- a/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx +++ b/DataFormats/Reconstruction/src/TrackParametrizationWithError.cxx @@ -21,6 +21,7 @@ #ifndef GPUCA_ALIGPUCODE #include +#include "ReconstructionDataFormats/TrackFwd.h" #endif using namespace o2::track; @@ -43,7 +44,7 @@ GPUd() void TrackParametrizationWithError::invert() //______________________________________________________________ template -GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, value_t b) +GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, value_t bz) { //---------------------------------------------------------------- // propagate this track to the plane X=xk (cm) in the field "b" (kG) @@ -52,7 +53,7 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, valu if (gpu::CAMath::Abs(dx) < constants::math::Almost0) { return true; } - value_t crv = this->getCurvature(b); + value_t crv = this->getCurvature(bz); value_t x2r = crv * dx; value_t f1 = this->getSnp(), f2 = f1 + x2r; if ((gpu::CAMath::Abs(f1) > constants::math::Almost1) || (gpu::CAMath::Abs(f2) > constants::math::Almost1)) { @@ -66,7 +67,8 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, valu if (gpu::CAMath::Abs(r2) < constants::math::Almost0) { return false; } - double dy2dx = (f1 + f2) / (r1 + r2); + double r1pr2Inv = 1. / (r1 + r2); + double dy2dx = (f1 + f2) * r1pr2Inv; bool arcz = gpu::CAMath::Abs(x2r) > 0.05f; params_t dP{0.f}; if (arcz) { @@ -106,14 +108,110 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, valu &c44 = mC[kSigQ2Pt2]; // evaluate matrix in double prec. - double rinv = 1. / r1; - double r3inv = rinv * rinv * rinv; - double f24 = dx * b * constants::math::B2C; // x2r/mP[kQ2Pt]; - double f02 = dx * r3inv; - double f04 = 0.5 * f24 * f02; - double f12 = f02 * this->getTgl() * f1; - double f14 = 0.5 * f24 * f12; // 0.5*f24*f02*getTgl()*f1; - double f13 = dx * rinv; + value_t kb = bz * constants::math::B2C; + double r2inv = 1. / r2, r1inv = 1. / r1; + double dx2r1pr2 = dx * r1pr2Inv; + + double hh = dx2r1pr2 * r2inv * (1. + r1 * r2 + f1 * f2), jj = dx * (dy2dx - f2 * r2inv); + double f02 = hh * r1inv; + double f04 = hh * dx2r1pr2 * kb; + double f24 = dx * kb; // x2r/mP[kQ2Pt]; + double f12 = this->getTgl() * (f02 * f2 + jj); + double f13 = dx * (r2 + f2 * dy2dx); + double f14 = this->getTgl() * (f04 * f2 + jj * f24); + + // b = C*ft + double b00 = f02 * c20 + f04 * c40, b01 = f12 * c20 + f14 * c40 + f13 * c30; + double b02 = f24 * c40; + double b10 = f02 * c21 + f04 * c41, b11 = f12 * c21 + f14 * c41 + f13 * c31; + double b12 = f24 * c41; + double b20 = f02 * c22 + f04 * c42, b21 = f12 * c22 + f14 * c42 + f13 * c32; + double b22 = f24 * c42; + double b40 = f02 * c42 + f04 * c44, b41 = f12 * c42 + f14 * c44 + f13 * c43; + double b42 = f24 * c44; + double b30 = f02 * c32 + f04 * c43, b31 = f12 * c32 + f14 * c43 + f13 * c33; + double b32 = f24 * c43; + + // a = f*b = f*C*ft + double a00 = f02 * b20 + f04 * b40, a01 = f02 * b21 + f04 * b41, a02 = f02 * b22 + f04 * b42; + double a11 = f12 * b21 + f14 * b41 + f13 * b31, a12 = f12 * b22 + f14 * b42 + f13 * b32; + double a22 = f24 * b42; + + // F*C*Ft = C + (b + bt + a) + c00 += b00 + b00 + a00; + c10 += b10 + b01 + a01; + c20 += b20 + b02 + a02; + c30 += b30; + c40 += b40; + c11 += b11 + b11 + a11; + c21 += b21 + b12 + a12; + c31 += b31; + c41 += b41; + c22 += b22 + b22 + a22; + c32 += b32; + c42 += b42; + + checkCovariance(); + + return true; +} + +//______________________________________________________________ +template +GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, TrackParametrization& linRef0, value_t bz) +{ + //---------------------------------------------------------------- + // propagate this track to the plane X=xk (cm) in the field "b" (kG), using linRef as linearization point + //---------------------------------------------------------------- + if (this->getAbsCharge() == 0) { + bz = 0; + } + value_t dx = xk - this->getX(); + if (gpu::CAMath::Abs(dx) < constants::math::Almost0) { + this->setX(xk); + linRef0.setX(xk); + return true; + } + // propagate reference track + TrackParametrization linRef1 = linRef0; + if (!linRef1.propagateTo(xk, bz)) { + return false; + } + value_t kb = bz * constants::math::B2C; + // evaluate in double prec. + double snpRef0 = linRef0.getSnp(), cspRef0 = gpu::CAMath::Sqrt((1 - snpRef0) * (1 + snpRef0)); + double snpRef1 = linRef1.getSnp(), cspRef1 = gpu::CAMath::Sqrt((1 - snpRef1) * (1 + snpRef1)); + double cspRef0Inv = 1 / cspRef0, cspRef1Inv = 1 / cspRef1, cc = cspRef0 + cspRef1, ccInv = 1 / cc, dy2dx = (snpRef0 + snpRef1) * ccInv; + double dxccInv = dx * ccInv, hh = dxccInv * cspRef1Inv * (1 + cspRef0 * cspRef1 + snpRef0 * snpRef1), jj = dx * (dy2dx - snpRef1 * cspRef1Inv); + + double f02 = hh * cspRef0Inv; + double f04 = hh * dxccInv * kb; + double f24 = dx * kb; + double f12 = linRef0.getTgl() * (f02 * snpRef1 + jj); + double f13 = dx * (cspRef1 + snpRef1 * dy2dx); // dS + double f14 = linRef0.getTgl() * (f04 * snpRef1 + jj * f24); + + // difference between the current and reference state + value_t diff[5]; + for (int i = 0; i < 5; i++) { + diff[i] = this->getParam(i) - linRef0.getParam(i); + } + value_t snpUpd = snpRef1 + diff[kSnp] + f24 * diff[kQ2Pt]; + if (gpu::CAMath::Abs(snpUpd) > constants::math::Almost1) { + return false; + } + linRef0 = linRef1; // update reference track + this->setX(xk); + this->setY(linRef1.getY() + diff[kY] + f02 * diff[kSnp] + f04 * diff[kQ2Pt]); + this->setZ(linRef1.getZ() + diff[kZ] + f13 * diff[kTgl] + f14 * diff[kQ2Pt]); + this->setSnp(snpUpd); + this->setTgl(linRef1.getTgl() + diff[kTgl]); + this->setQ2Pt(linRef1.getQ2Pt() + diff[kQ2Pt]); + + value_t &c00 = mC[kSigY2], &c10 = mC[kSigZY], &c11 = mC[kSigZ2], &c20 = mC[kSigSnpY], &c21 = mC[kSigSnpZ], + &c22 = mC[kSigSnp2], &c30 = mC[kSigTglY], &c31 = mC[kSigTglZ], &c32 = mC[kSigTglSnp], &c33 = mC[kSigTgl2], + &c40 = mC[kSigQ2PtY], &c41 = mC[kSigQ2PtZ], &c42 = mC[kSigQ2PtSnp], &c43 = mC[kSigQ2PtTgl], + &c44 = mC[kSigQ2Pt2]; // b = C*ft double b00 = f02 * c20 + f04 * c40, b01 = f12 * c20 + f14 * c40 + f13 * c30; @@ -158,6 +256,7 @@ GPUd() bool TrackParametrizationWithError::testRotate(value_t) const // no ops return true; } + //______________________________________________________________ template GPUd() bool TrackParametrizationWithError::rotate(value_t alpha) @@ -213,6 +312,101 @@ GPUd() bool TrackParametrizationWithError::rotate(value_t alpha) return true; } +//______________________________________________________________ +template +GPUd() bool TrackParametrizationWithError::rotate(value_t alpha, TrackParametrization& linRef0, value_t bz) +{ + // RS: similar to int32_t GPUTPCGMPropagator::RotateToAlpha(float newAlpha), i.e. rotate the track to new frame alpha, using linRef as linearization point + // rotate to alpha frame the reference (linearization point) trackParam, then align the current track to it + if (gpu::CAMath::Abs(this->getSnp()) > constants::math::Almost1) { + LOGP(debug, "Precondition is not satisfied: |sin(phi)|>1 ! {:f}", this->getSnp()); + return false; + } + // + math_utils::detail::bringToPMPi(alpha); + // + value_t ca = 0, sa = 0; + TrackParametrization linRef1 = linRef0; + // rotate the reference, adjusting alpha to +-pi, return precalculated cos and sin of alpha - alphaOld + if (!linRef1.rotateParam(alpha, ca, sa)) { + return false; + } + + value_t trackX = this->getX() * ca + this->getY() * sa; // X of the rotated current track + if (!linRef1.propagateParamTo(trackX, bz)) { + return false; + } + + // now rotate the current track + value_t snp = this->getSnp(), csp = gpu::CAMath::Sqrt((1.f - snp) * (1.f + snp)), updSnp = snp * ca - csp * sa; + if ((csp * ca + snp * sa) < 0 || gpu::CAMath::Abs(updSnp) > constants::math::Almost1) { + // LOGP(warning,"Rotation failed: local cos(phi) would become {:.2f}", csp * ca + snp * sa); + return false; + } + this->setY(-sa * this->getX() + ca * this->getY()); + this->setX(trackX); + this->setSnp(updSnp); + this->setAlpha(alpha); + + // rotate covariance, accounting for the extra error from the rotated X + value_t snpRef0 = linRef0.getSnp(), cspRef0 = gpu::CAMath::Sqrt((value_t(1) - snpRef0) * (value_t(1) + snpRef0)); // original reference + value_t snpRef1 = linRef1.getSnp(), cspRef1 = ca * cspRef0 + sa * snpRef0; // rotated reference + value_t rr = cspRef1 / cspRef0; // cos1_ref / cos0_ref + + // "extra row" of the lower triangle of cov. matrix + value_t cXSigY = mC[kSigY2] * ca * sa; + value_t cXSigZ = mC[kSigZY] * sa; + value_t cXSigSnp = mC[kSigSnpY] * rr * sa; + value_t cXSigTgl = mC[kSigTglY] * sa; + value_t cXSigQ2Pt = mC[kSigQ2PtY] * sa; + value_t cSigX2 = mC[kSigY2] * sa * sa; + + // plane rotation of existing cov matrix + mC[kSigY2] *= ca * ca; + mC[kSigZY] *= ca; + mC[kSigSnpY] *= ca * rr; + mC[kSigSnpZ] *= rr; + mC[kSigSnp2] *= rr * rr; + mC[kSigTglY] *= ca; + mC[kSigTglSnp] *= rr; + mC[kSigQ2PtY] *= ca; + mC[kSigQ2PtSnp] *= rr; + + // transport covariance from pseudo 6x6 matrix to usual 5x5, Jacobian (trust to Sergey): + auto cspRef1Inv = value_t(1) / cspRef1; + auto j3 = -snpRef1 * cspRef1Inv; // -pYmod/pXmod = -tg_pho = -sin_phi_mod / cos_phi_mod + auto j4 = -linRef1.getTgl() * cspRef1Inv; // -pZmod/pXmod = -tgl_mod / cos_phi_mod + auto j5 = linRef1.getCurvature(bz); + // Y Z Sin DzDs q/p X + // { { 1, 0, 0, 0, 0, j3 }, // Y + // { 0, 1, 0, 0, 0, j4 }, // Z + // { 0, 0, 1, 0, 0, j5 }, // snp + // { 0, 0, 0, 1, 0, 0 }, // tgl + // { 0, 0, 0, 0, 1, 0 } }; // q/pt + auto hXSigY = cXSigY + cSigX2 * j3; + auto hXSigZ = cXSigZ + cSigX2 * j4; + auto hXSigSnp = cXSigSnp + cSigX2 * j5; + + mC[kSigY2] += j3 * (cXSigY + hXSigY); + mC[kSigZ2] += j4 * (cXSigZ + hXSigZ); + mC[kSigSnpY] += cXSigSnp * j3 + hXSigY * j5; + mC[kSigSnp2] += j5 * (cXSigSnp + hXSigSnp); + mC[kSigTglZ] += cXSigTgl * j4; + mC[kSigQ2PtY] += cXSigQ2Pt * j3; + mC[kSigQ2PtSnp] += cXSigQ2Pt * j5; + + mC[kSigZY] += cXSigZ * j3 + hXSigY * j4; + mC[kSigSnpZ] += cXSigSnp * j4 + hXSigZ * j5; + mC[kSigTglY] += cXSigTgl * j3; + mC[kSigTglSnp] += cXSigTgl * j5; + mC[kSigQ2PtZ] += cXSigQ2Pt * j4; + + checkCovariance(); + linRef0 = linRef1; + + return true; +} + //_______________________________________________________________________ template GPUd() bool TrackParametrizationWithError::propagateToDCA(const o2::dataformats::VertexBase& vtx, value_t b, o2::dataformats::DCA* dca, value_t maxD) @@ -227,6 +421,10 @@ GPUd() bool TrackParametrizationWithError::propagateToDCA(const o2::dat // Estimate the impact parameter neglecting the track curvature value_t d = gpu::CAMath::Abs(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } value_t crv = this->getCurvature(b); @@ -245,6 +443,10 @@ GPUd() bool TrackParametrizationWithError::propagateToDCA(const o2::dat #if !defined(GPUCA_ALIGPUCODE) LOG(debug) << "failed to propagate to alpha=" << alp << " X=" << xv << vtx << " | Track is: " << tmpT.asString(); #endif + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } *this = tmpT; @@ -468,8 +670,8 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, cons if (gpu::CAMath::Abs(r2) < constants::math::Almost0) { return false; } - - value_t dy2dx = (f1 + f2) / (r1 + r2); + double r1pr2Inv = 1. / (r1 + r2), r2inv = 1. / r2, r1inv = 1. / r1; + double dy2dx = (f1 + f2) * r1pr2Inv, dx2r1pr2 = dx * r1pr2Inv; value_t step = (gpu::CAMath::Abs(x2r) < 0.05f) ? dx * gpu::CAMath::Abs(r2 + f2 * dy2dx) // chord : 2.f * gpu::CAMath::ASin(0.5f * dx * gpu::CAMath::Sqrt(1.f + dy2dx * dy2dx) * crv) / crv; // arc step *= gpu::CAMath::Sqrt(1.f + this->getTgl() * this->getTgl()); @@ -485,15 +687,16 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, cons &c22 = mC[kSigSnp2], &c30 = mC[kSigTglY], &c31 = mC[kSigTglZ], &c32 = mC[kSigTglSnp], &c33 = mC[kSigTgl2], &c40 = mC[kSigQ2PtY], &c41 = mC[kSigQ2PtZ], &c42 = mC[kSigQ2PtSnp], &c43 = mC[kSigQ2PtTgl], &c44 = mC[kSigQ2Pt2]; + // evaluate matrix in double prec. - double rinv = 1. / r1; - double r3inv = rinv * rinv * rinv; - double f24 = dx * b[2] * constants::math::B2C; // x2r/track[kQ2Pt]; - double f02 = dx * r3inv; - double f04 = 0.5 * f24 * f02; - double f12 = f02 * this->getTgl() * f1; - double f14 = 0.5 * f24 * f12; // 0.5*f24*f02*getTgl()*f1; - double f13 = dx * rinv; + value_t kb = b[2] * constants::math::B2C; + double hh = dx2r1pr2 * r2inv * (1. + r1 * r2 + f1 * f2), jj = dx * (dy2dx - f2 * r2inv); + double f02 = hh * r1inv; + double f04 = hh * dx2r1pr2 * kb; + double f24 = dx * kb; // x2r/mP[kQ2Pt]; + double f12 = this->getTgl() * (f02 * f2 + jj); + double f13 = dx * (r2 + f2 * dy2dx); + double f14 = this->getTgl() * (f04 * f2 + jj * f24); // b = C*ft double b00 = f02 * c20 + f04 * c40, b01 = f12 * c20 + f14 * c40 + f13 * c30; @@ -574,14 +777,14 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, cons // Do the final correcting step to the target plane (linear approximation) value_t x = vecLab[0], y = vecLab[1], z = vecLab[2]; - if (gpu::CAMath::Abs(dx) > constants::math::Almost0) { + if (gpu::CAMath::Abs(x - xk) > constants::math::Almost0) { if (gpu::CAMath::Abs(vecLab[3]) < constants::math::Almost0) { return false; } - dx = xk - vecLab[0]; - x += dx; - y += vecLab[4] / vecLab[3] * dx; - z += vecLab[5] / vecLab[3] * dx; + auto dxFin = xk - vecLab[0]; + x += dxFin; + y += vecLab[4] / vecLab[3] * dxFin; + z += vecLab[5] / vecLab[3] * dxFin; } // Calculate the track parameters @@ -596,6 +799,198 @@ GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, cons return true; } +//____________________________________________________________ +template +GPUd() bool TrackParametrizationWithError::propagateTo(value_t xk, TrackParametrization& linRef0, const dim3_t& b) +{ + //---------------------------------------------------------------- + // Extrapolate this track to the plane X=xk in the field b[]. + // + // X [cm] is in the "tracking coordinate system" of this track. + // b[]={Bx,By,Bz} [kG] is in the Global coordidate system. + //---------------------------------------------------------------- + + value_t dx = xk - this->getX(); + if (gpu::CAMath::Abs(dx) < constants::math::Almost0) { + return true; + } + // Do not propagate tracks outside the ALICE detector + if (gpu::CAMath::Abs(dx) > 1e5 || gpu::CAMath::Abs(this->getY()) > 1e5 || gpu::CAMath::Abs(this->getZ()) > 1e5) { + LOG(warning) << "Anomalous track, target X:" << xk; + // print(); + return false; + } + if (gpu::CAMath::Abs(dx) < constants::math::Almost0) { + this->setX(xk); + linRef0.setX(xk); + return true; + } + // preliminary calculations to find the step size + value_t crv = (gpu::CAMath::Abs(b[2]) < constants::math::Almost0) ? 0.f : linRef0.getCurvature(b[2]); + if (gpu::CAMath::Abs(crv) < constants::math::Almost0) { + return propagateTo(xk, linRef0, 0.); + } + value_t kb = b[2] * constants::math::B2C, x2r = crv * dx; + // evaluate in double prec. + value_t snpRef0 = linRef0.getSnp(), snpRef1 = snpRef0 + x2r; + if ((gpu::CAMath::Abs(snpRef0) > constants::math::Almost1) || (gpu::CAMath::Abs(snpRef1) > constants::math::Almost1)) { + return false; + } + value_t cspRef0 = gpu::CAMath::Sqrt((1 - snpRef0) * (1 + snpRef0)), cspRef1 = gpu::CAMath::Sqrt((1 - snpRef1) * (1 + snpRef1)); + if (gpu::CAMath::Abs(cspRef0) < constants::math::Almost0 || gpu::CAMath::Abs(cspRef1) < constants::math::Almost0) { + return false; + } + value_t cspRef0Inv = value_t(1) / cspRef0, cspRef1Inv = value_t(1) / cspRef1, cc = cspRef0 + cspRef1, ccInv = value_t(1) / cc, dy2dx = (snpRef0 + snpRef1) * ccInv; + value_t step = (gpu::CAMath::Abs(crv * dx) < 0.05f) ? dx * (cspRef1 + snpRef1 * dy2dx) : 2. * gpu::CAMath::ASin(0.5 * dx * gpu::CAMath::Sqrt(1.f + dy2dx * dy2dx) * crv) / crv; // arc + step *= gpu::CAMath::Sqrt(1.f + linRef0.getTgl() * linRef0.getTgl()); + + // + // get the track x,y,z,px/p,py/p,pz/p,p,sinAlpha,cosAlpha in the Global System + std::array vecLab{0.f}; + if (!linRef0.getPosDirGlo(vecLab)) { + return false; + } + // + // Rotate to the system where Bx=By=0. + value_t bxy2 = b[0] * b[0] + b[1] * b[1]; + value_t bt = gpu::CAMath::Sqrt(bxy2); + value_t cosphi = 1.f, sinphi = 0.f; + if (bt > constants::math::Almost0) { + cosphi = b[0] / bt; + sinphi = b[1] / bt; + } + value_t bb = gpu::CAMath::Sqrt(bxy2 + b[2] * b[2]); + value_t costet = 1., sintet = 0.; + if (bb > constants::math::Almost0) { + costet = b[2] / bb; + sintet = bt / bb; + } + std::array vect{costet * cosphi * vecLab[0] + costet * sinphi * vecLab[1] - sintet * vecLab[2], + -sinphi * vecLab[0] + cosphi * vecLab[1], + sintet * cosphi * vecLab[0] + sintet * sinphi * vecLab[1] + costet * vecLab[2], + costet * cosphi * vecLab[3] + costet * sinphi * vecLab[4] - sintet * vecLab[5], + -sinphi * vecLab[3] + cosphi * vecLab[4], + sintet * cosphi * vecLab[3] + sintet * sinphi * vecLab[4] + costet * vecLab[5], + vecLab[6]}; + + // Do the helix step + value_t q = this->getCharge(); + g3helx3(q * bb, step, vect); + + // Rotate back to the Global System + vecLab[0] = cosphi * costet * vect[0] - sinphi * vect[1] + cosphi * sintet * vect[2]; + vecLab[1] = sinphi * costet * vect[0] + cosphi * vect[1] + sinphi * sintet * vect[2]; + vecLab[2] = -sintet * vect[0] + costet * vect[2]; + + vecLab[3] = cosphi * costet * vect[3] - sinphi * vect[4] + cosphi * sintet * vect[5]; + vecLab[4] = sinphi * costet * vect[3] + cosphi * vect[4] + sinphi * sintet * vect[5]; + vecLab[5] = -sintet * vect[3] + costet * vect[5]; + + // Rotate back to the Tracking System + value_t sinalp = -vecLab[7], cosalp = vecLab[8]; + value_t t = cosalp * vecLab[0] - sinalp * vecLab[1]; + vecLab[1] = sinalp * vecLab[0] + cosalp * vecLab[1]; + vecLab[0] = t; + t = cosalp * vecLab[3] - sinalp * vecLab[4]; + vecLab[4] = sinalp * vecLab[3] + cosalp * vecLab[4]; + vecLab[3] = t; + + // Do the final correcting step to the target plane (linear approximation) + value_t x = vecLab[0], y = vecLab[1], z = vecLab[2]; + if (gpu::CAMath::Abs(x - xk) > constants::math::Almost0) { + if (gpu::CAMath::Abs(vecLab[3]) < constants::math::Almost0) { + return false; + } + auto dxFin = xk - vecLab[0]; + x += dxFin; + y += vecLab[4] / vecLab[3] * dxFin; + z += vecLab[5] / vecLab[3] * dxFin; + } + + // Calculate the track parameters + auto linRef1 = linRef0; + t = 1.f / gpu::CAMath::Sqrt(vecLab[3] * vecLab[3] + vecLab[4] * vecLab[4]); + linRef1.setX(xk); + linRef1.setY(y); + linRef1.setZ(z); + linRef1.setSnp(snpRef1 = vecLab[4] * t); // reassign snpRef1 + linRef1.setTgl(vecLab[5] * t); + linRef1.setQ2Pt(q * t / vecLab[6]); + + // recalculate parameters of the transported ref track needed for transport of this: + cspRef1 = gpu::CAMath::Sqrt((1 - snpRef1) * (1 + snpRef1)); + cspRef1Inv = value_t(1) / cspRef1; + cc = cspRef0 + cspRef1; + ccInv = value_t(1) / cc; + dy2dx = (snpRef0 + snpRef1) * ccInv; + double dxccInv = dx * ccInv, hh = dxccInv * cspRef1Inv * (1 + cspRef0 * cspRef1 + snpRef0 * snpRef1), jj = dx * (dy2dx - snpRef1 * cspRef1Inv); + double f02 = hh * cspRef0Inv; + double f04 = hh * dxccInv * kb; + double f24 = dx * kb; + double f12 = linRef0.getTgl() * (f02 * snpRef1 + jj); + double f13 = dx * (cspRef1 + snpRef1 * dy2dx); // dS + double f14 = linRef0.getTgl() * (f04 * snpRef1 + jj * f24); + + // difference between the current and reference state + value_t diff[5]; + for (int i = 0; i < 5; i++) { + diff[i] = this->getParam(i) - linRef0.getParam(i); + } + value_t snpUpd = snpRef1 + diff[kSnp] + f24 * diff[kQ2Pt]; + if (gpu::CAMath::Abs(snpUpd) > constants::math::Almost1) { + return false; + } + this->setX(xk); + this->setY(linRef1.getY() + diff[kY] + f02 * diff[kSnp] + f04 * diff[kQ2Pt]); + this->setZ(linRef1.getZ() + diff[kZ] + f13 * diff[kTgl] + f14 * diff[kQ2Pt]); + this->setSnp(snpUpd); + this->setTgl(linRef1.getTgl() + diff[kTgl]); + this->setQ2Pt(linRef1.getQ2Pt() + diff[kQ2Pt]); + + linRef0 = linRef1; // update reference track + + // matrix transformed with Bz component only + value_t &c00 = mC[kSigY2], &c10 = mC[kSigZY], &c11 = mC[kSigZ2], &c20 = mC[kSigSnpY], &c21 = mC[kSigSnpZ], + &c22 = mC[kSigSnp2], &c30 = mC[kSigTglY], &c31 = mC[kSigTglZ], &c32 = mC[kSigTglSnp], &c33 = mC[kSigTgl2], + &c40 = mC[kSigQ2PtY], &c41 = mC[kSigQ2PtZ], &c42 = mC[kSigQ2PtSnp], &c43 = mC[kSigQ2PtTgl], + &c44 = mC[kSigQ2Pt2]; + + // b = C*ft + double b00 = f02 * c20 + f04 * c40, b01 = f12 * c20 + f14 * c40 + f13 * c30; + double b02 = f24 * c40; + double b10 = f02 * c21 + f04 * c41, b11 = f12 * c21 + f14 * c41 + f13 * c31; + double b12 = f24 * c41; + double b20 = f02 * c22 + f04 * c42, b21 = f12 * c22 + f14 * c42 + f13 * c32; + double b22 = f24 * c42; + double b40 = f02 * c42 + f04 * c44, b41 = f12 * c42 + f14 * c44 + f13 * c43; + double b42 = f24 * c44; + double b30 = f02 * c32 + f04 * c43, b31 = f12 * c32 + f14 * c43 + f13 * c33; + double b32 = f24 * c43; + + // a = f*b = f*C*ft + double a00 = f02 * b20 + f04 * b40, a01 = f02 * b21 + f04 * b41, a02 = f02 * b22 + f04 * b42; + double a11 = f12 * b21 + f14 * b41 + f13 * b31, a12 = f12 * b22 + f14 * b42 + f13 * b32; + double a22 = f24 * b42; + + // F*C*Ft = C + (b + bt + a) + c00 += b00 + b00 + a00; + c10 += b10 + b01 + a01; + c20 += b20 + b02 + a02; + c30 += b30; + c40 += b40; + c11 += b11 + b11 + a11; + c21 += b21 + b12 + a12; + c31 += b31; + c41 += b41; + c22 += b22 + b22 + a22; + c32 += b32; + c42 += b42; + + checkCovariance(); + + return true; +} + //______________________________________________ template GPUd() void TrackParametrizationWithError::checkCorrelations() @@ -1113,6 +1508,143 @@ GPUd() bool TrackParametrizationWithError::correctForMaterial(value_t x return true; } +//______________________________________________ +template +GPUd() bool TrackParametrizationWithError::correctForMaterial(TrackParametrization& linRef, value_t x2x0, value_t xrho, bool anglecorr) +{ + //------------------------------------------------------------------ + // This function corrects the reference and current track parameters for the crossed material + // "x2x0" - X/X0, the thickness in units of the radiation length. + // "xrho" - is the product length*density (g/cm^2). + // It should be passed as negative when propagating tracks + // from the intreaction point to the outside of the central barrel. + // "dedx" - mean enery loss (GeV/(g/cm^2), if <=kCalcdEdxAuto : calculate on the fly + // "anglecorr" - switch for the angular correction + //------------------------------------------------------------------ + constexpr value_t kMSConst2 = 0.0136f * 0.0136f; + constexpr value_t kMinP = 0.01f; // kill below this momentum + + value_t csp2 = (1.f - linRef.getSnp()) * (1.f + linRef.getSnp()); // cos(phi)^2 + value_t cst2I = (1.f + linRef.getTgl() * linRef.getTgl()); // 1/cos(lambda)^2 + if (anglecorr) { // Apply angle correction, if requested + value_t angle = gpu::CAMath::Sqrt(cst2I / csp2); + x2x0 *= angle; + xrho *= angle; + } + auto pid = linRef.getPID(); + auto m = pid.getMass(); + int charge2 = linRef.getAbsCharge() * linRef.getAbsCharge(); + value_t p = linRef.getP(), p0 = p, p02 = p * p, e2 = p02 + pid.getMass2(), massInv = 1. / m, bg = p * massInv, dETot = 0.; + value_t e = gpu::CAMath::Sqrt(e2), e0 = e; + if (m > 0 && xrho != 0.f) { + value_t ekin = e - m, dedx = this->getdEdxBBOpt(bg); +#ifdef _BB_NONCONST_CORR_ + value_t dedxDer = 0., dedx1 = dedx; +#endif + if (charge2 != 1) { + dedx *= charge2; + } + value_t dE = dedx * xrho; + int na = 1 + int(gpu::CAMath::Abs(dE) / ekin * ELoss2EKinThreshInv); + if (na > MaxELossIter) { + na = MaxELossIter; + } + if (na > 1) { + dE /= na; + xrho /= na; +#ifdef _BB_NONCONST_CORR_ + dedxDer = this->getBetheBlochSolidDerivativeApprox(dedx1, bg); // require correction for non-constantness of dedx vs betagamma + if (charge2 != 1) { + dedxDer *= charge2; + } +#endif + } + while (na--) { +#ifdef _BB_NONCONST_CORR_ + if (dedxDer != 0.) { // correction for non-constantness of dedx vs beta*gamma (in linear approximation): for a single step dE -> dE * [(exp(dedxDer) - 1)/dedxDer] + if (xrho < 0) { + dedxDer = -dedxDer; // E.loss ( -> positive derivative) + } + auto corrC = (gpu::CAMath::Exp(dedxDer) - 1.) / dedxDer; + dE *= corrC; + } +#endif + e += dE; + if (e > m) { // stopped + p = gpu::CAMath::Sqrt(e * e - pid.getMass2()); + } else { + return false; + } + if (na) { + bg = p * massInv; + dedx = this->getdEdxBBOpt(bg); +#ifdef _BB_NONCONST_CORR_ + dedxDer = this->getBetheBlochSolidDerivativeApprox(dedx, bg); +#endif + if (charge2 != 1) { + dedx *= charge2; +#ifdef _BB_NONCONST_CORR_ + dedxDer *= charge2; +#endif + } + dE = dedx * xrho; + } + } + + if (p < kMinP) { + return false; + } + dETot = e - e0; + } // end of e.loss correction + + // Calculating the multiple scattering corrections****************** + value_t& fC22 = mC[kSigSnp2]; + value_t& fC33 = mC[kSigTgl2]; + value_t& fC43 = mC[kSigQ2PtTgl]; + value_t& fC44 = mC[kSigQ2Pt2]; + // + value_t cC22(0.f), cC33(0.f), cC43(0.f), cC44(0.f); + if (x2x0 != 0.f) { + value_t beta2 = p02 / e2, theta2 = kMSConst2 / (beta2 * p02) * gpu::CAMath::Abs(x2x0); + value_t fp34 = linRef.getTgl(); + if (charge2 != 1) { + theta2 *= charge2; + fp34 *= linRef.getCharge2Pt(); + } + if (theta2 > constants::math::PI * constants::math::PI) { + return false; + } + value_t t2c2I = theta2 * cst2I; + cC22 = t2c2I * csp2; + cC33 = t2c2I * cst2I; + cC43 = t2c2I * fp34; + cC44 = theta2 * fp34 * fp34; + // optimize this + // cC22 = theta2*((1.-getSnp())*(1.+getSnp()))*(1. + this->getTgl()*getTgl()); + // cC33 = theta2*(1. + this->getTgl()*getTgl())*(1. + this->getTgl()*getTgl()); + // cC43 = theta2*getTgl()*this->getQ2Pt()*(1. + this->getTgl()*getTgl()); + // cC44 = theta2*getTgl()*this->getQ2Pt()*getTgl()*this->getQ2Pt(); + } + + // the energy loss correction contribution to cov.matrix: approximate energy loss fluctuation (M.Ivanov) + constexpr value_t knst = 0.0007f; // To be tuned. + value_t sigmadE = knst * gpu::CAMath::Sqrt(gpu::CAMath::Abs(dETot)) * e0 / p02 * linRef.getCharge2Pt(); + cC44 += sigmadE * sigmadE; + + // Applying the corrections***************************** + fC22 += cC22; + fC33 += cC33; + fC43 += cC43; + fC44 += cC44; + auto pscale = p0 / p; + linRef.setQ2Pt(linRef.getQ2Pt() * pscale); + this->setQ2Pt(this->getQ2Pt() * pscale); + + checkCovariance(); + + return true; +} + //______________________________________________________________ template GPUd() bool TrackParametrizationWithError::getCovXYZPxPyPzGlo(std::array& cv) const @@ -1237,6 +1769,64 @@ GPUd() void TrackParametrizationWithError::printHexadecimal() #endif } +#ifndef GPUCA_ALIGPUCODE +//______________________________________________________________ +template +bool TrackParametrizationWithError::toFwdTrackParCov(TrackParCovFwd& t) const +{ + auto p = this->getXYZGlo(); + t.setZ(p.Z()); + t.setX(p.X()); + t.setY(p.Y()); + t.setPhi(this->getPhi()); + t.setTanl(this->getTgl()); + t.setInvQPt(this->getQ2Pt()); + // + if (gpu::CAMath::Abs(this->getSnp()) >= o2::constants::math::Almost1 || + gpu::CAMath::Abs(this->getTgl()) <= o2::constants::math::Almost0) { + return false; + } + value_T csa, sna, csP, snP, csp = gpu::CAMath::Sqrt((1. - this->getSnp()) * (1. + this->getSnp())); + math_utils::detail::sincos(value_T(this->getAlpha()), sna, csa); + math_utils::detail::sincos(value_T(t.getPhi()), snP, csP); + /* + Jacobian is + /-sna -csP/tgL 0 0 0 \ + | csa -snP/tgL 0 0 0 | + | 0 0 1/csp 0 0 | + | 0 0 0 1 0 | + \ 0 0 0 0 1 / + */ + auto tgLI = 1 / this->getTgl(); + const value_T d1 = -sna; + const value_T d2 = -csP * tgLI; + const value_T e1 = csa; + const value_T e2 = -snP * tgLI; + const value_T f1 = 1 / csp; + SMatrix55Sym C; + C(0, 0) = d1 * d1 * getSigmaY2() + 2 * d1 * d2 * getSigmaZY() + d2 * d2 * getSigmaZ2(); + C(0, 1) = d1 * e1 * getSigmaY2() + (d1 * e2 + d2 * e1) * getSigmaZY() + d2 * e2 * getSigmaZ2(); + C(1, 1) = e1 * e1 * getSigmaY2() + 2 * e1 * e2 * getSigmaZY() + e2 * e2 * getSigmaZ2(); + + C(0, 2) = f1 * (d1 * getSigmaSnpY() + d2 * getSigmaSnpZ()); + C(1, 2) = f1 * (e1 * getSigmaSnpY() + e2 * getSigmaSnpZ()); + C(2, 2) = f1 * f1 * getSigmaSnp2(); + + C(0, 3) = d1 * getSigmaTglY() + d2 * getSigmaTglZ(); + C(1, 3) = e1 * getSigmaTglY() + e2 * getSigmaTglZ(); + C(2, 3) = f1 * getSigmaTglSnp(); + C(3, 3) = getSigmaTgl2(); + + C(0, 4) = d1 * getSigma1PtY() + d2 * getSigma1PtZ(); + C(1, 4) = e1 * getSigma1PtY() + e2 * getSigma1PtZ(); + C(2, 4) = f1 * getSigma1PtSnp(); + C(3, 4) = getSigma1PtTgl(); + C(4, 4) = getSigma1Pt2(); + t.setCovariances(C); + return true; +} +#endif + namespace o2::track { #if !defined(GPUCA_GPUCODE) || defined(GPUCA_GPUCODE_DEVICE) // FIXME: DR: WORKAROUND to avoid CUDA bug creating host symbols for device code. 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/BunchFilling.h b/DataFormats/common/include/CommonDataFormat/BunchFilling.h index 182a665532668..f11ce2498d04b 100644 --- a/DataFormats/common/include/CommonDataFormat/BunchFilling.h +++ b/DataFormats/common/include/CommonDataFormat/BunchFilling.h @@ -107,7 +107,6 @@ class BunchFilling ClassDefNV(BunchFilling, 2); }; -} // namespace o2 namespace framework { @@ -118,5 +117,6 @@ struct is_messageable : std::true_type { }; } // namespace framework +} // namespace o2 #endif 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/RangeReference.h b/DataFormats/common/include/CommonDataFormat/RangeReference.h index 0308d3b8af937..3d0c58298de03 100644 --- a/DataFormats/common/include/CommonDataFormat/RangeReference.h +++ b/DataFormats/common/include/CommonDataFormat/RangeReference.h @@ -29,23 +29,23 @@ template class RangeReference { public: - GPUd() RangeReference(FirstEntry ent, NElem n) { set(ent, n); } - GPUdDefault() RangeReference(const RangeReference& src) = default; - GPUdDefault() RangeReference() = default; - GPUdDefault() ~RangeReference() = default; - GPUd() void set(FirstEntry ent, NElem n) + GPUhd() RangeReference(FirstEntry ent, NElem n) { set(ent, n); } + GPUhdDefault() RangeReference(const RangeReference& src) = default; + GPUhdDefault() RangeReference() = default; + GPUhdDefault() ~RangeReference() = default; + GPUhd() void set(FirstEntry ent, NElem n) { mFirstEntry = ent; mEntries = n; } - GPUd() void clear() { set(0, 0); } - GPUd() FirstEntry getFirstEntry() const { return mFirstEntry; } - GPUd() FirstEntry getEntriesBound() const { return mFirstEntry + mEntries; } - GPUd() NElem getEntries() const { return mEntries; } - GPUd() void setFirstEntry(FirstEntry ent) { mFirstEntry = ent; } - GPUd() void setEntries(NElem n) { mEntries = n; } - GPUd() void changeEntriesBy(NElem inc) { mEntries += inc; } - GPUd() bool operator==(const RangeReference& other) const + GPUhd() void clear() { set(0, 0); } + GPUhd() FirstEntry getFirstEntry() const { return mFirstEntry; } + GPUhd() FirstEntry getEntriesBound() const { return mFirstEntry + mEntries; } + GPUhd() NElem getEntries() const { return mEntries; } + GPUhd() void setFirstEntry(FirstEntry ent) { mFirstEntry = ent; } + GPUhd() void setEntries(NElem n) { mEntries = n; } + GPUhd() void changeEntriesBy(NElem inc) { mEntries += inc; } + GPUhd() bool operator==(const RangeReference& other) const { return mFirstEntry == other.mFirstEntry && mEntries == other.mEntries; } @@ -68,21 +68,21 @@ class RangeRefComp static constexpr Base MaskN = ((0x1 << NBitsN) - 1); static constexpr Base MaskR = (~Base(0)) & (~MaskN); Base mData = 0; ///< packed 1st entry reference + N entries - GPUd() void sanityCheck() + GPUhd() void sanityCheck() { static_assert(NBitsN < NBitsTotal, "NBitsN too large"); } public: - GPUd() RangeRefComp(int ent, int n) { set(ent, n); } - GPUdDefault() RangeRefComp() = default; - GPUdDefault() RangeRefComp(const RangeRefComp& src) = default; + GPUhd() RangeRefComp(int ent, int n) { set(ent, n); } + GPUhdDefault() RangeRefComp() = default; + GPUhdDefault() RangeRefComp(const RangeRefComp& src) = default; GPUhd() void set(int ent, int n) { mData = (Base(ent) << NBitsN) + (Base(n) & MaskN); } - GPUd() static constexpr Base getMaxFirstEntry() { return MaskR >> NBitsN; } - GPUd() static constexpr Base getMaxEntries() { return MaskN; } + GPUhd() static constexpr Base getMaxFirstEntry() { return MaskR >> NBitsN; } + GPUhd() static constexpr Base getMaxEntries() { return MaskN; } GPUhd() int getFirstEntry() const { return mData >> NBitsN; } GPUhd() int getEntries() const { return mData & ((0x1 << NBitsN) - 1); } GPUhd() int getEntriesBound() const { return getFirstEntry() + getEntries(); } 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/CommonDataFormatLinkDef.h b/DataFormats/common/src/CommonDataFormatLinkDef.h index 631305cd28f13..d66e89af637cc 100644 --- a/DataFormats/common/src/CommonDataFormatLinkDef.h +++ b/DataFormats/common/src/CommonDataFormatLinkDef.h @@ -26,10 +26,12 @@ #pragma link C++ class o2::dataformats::TimeStamp < float> + ; #pragma link C++ class o2::dataformats::TimeStamp < double> + ; #pragma link C++ class o2::dataformats::TimeStamp < int> + ; -#pragma link C++ class o2::dataformats::TimeStamp < Float16_t > + ; +#pragma link C++ class o2::dataformats::TimeStamp < uint32_t> + ; +#pragma link C++ class o2::dataformats::TimeStamp < Float16_t> + ; #pragma link C++ class o2::dataformats::TimeStampWithError < float, float> + ; #pragma link C++ class o2::dataformats::TimeStampWithError < double, double> + ; #pragma link C++ class o2::dataformats::TimeStampWithError < int, int> + ; +#pragma link C++ class o2::dataformats::TimeStampWithError < uint32_t, uint16_t> + ; #pragma link C++ class o2::dataformats::EvIndex < int, int> + ; #pragma link C++ class o2::dataformats::RangeReference < int, int> + ; 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/CMakeLists.txt b/DataFormats/simulation/CMakeLists.txt index fac67cc927562..33c91337c77e9 100644 --- a/DataFormats/simulation/CMakeLists.txt +++ b/DataFormats/simulation/CMakeLists.txt @@ -55,6 +55,11 @@ o2_target_root_dictionary( # * src/SimulationDataLinkDef.h # * and not src/SimulationDataFormatLinkDef.h +o2_add_test(InteractionSampler + SOURCES test/testInteractionSampler.cxx + COMPONENT_NAME SimulationDataFormat + PUBLIC_LINK_LIBRARIES O2::SimulationDataFormat) + o2_add_test(BasicHits SOURCES test/testBasicHits.cxx COMPONENT_NAME SimulationDataFormat diff --git a/DataFormats/simulation/include/SimulationDataFormat/BaseHits.h b/DataFormats/simulation/include/SimulationDataFormat/BaseHits.h index b9ed356ec8b5a..d1e1ee357c1cf 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/BaseHits.h +++ b/DataFormats/simulation/include/SimulationDataFormat/BaseHits.h @@ -49,13 +49,13 @@ class BasicXYZVHit : public BaseHit math_utils::Point3D mPos; // cartesian position of Hit E mTime; // time of flight V mHitValue; // hit value - short mDetectorID; // the detector/sensor id + unsigned short mDetectorID; // the detector/sensor id public: BasicXYZVHit() = default; // for ROOT IO // constructor - BasicXYZVHit(T x, T y, T z, E time, V val, int trackid, short did) + BasicXYZVHit(T x, T y, T z, E time, V val, int trackid, unsigned short did) : mPos(x, y, z), mTime(time), mHitValue(val), BaseHit(trackid), mDetectorID(did) { } @@ -70,12 +70,12 @@ class BasicXYZVHit : public BaseHit // getting the time E GetTime() const { return mTime; } // get detector + track information - short GetDetectorID() const { return mDetectorID; } + unsigned short GetDetectorID() const { return mDetectorID; } // modifiers void SetTime(E time) { mTime = time; } void SetHitValue(V val) { mHitValue = val; } - void SetDetectorID(short detID) { mDetectorID = detID; } + void SetDetectorID(unsigned short detID) { mDetectorID = detID; } void SetX(T x) { mPos.SetX(x); } void SetY(T y) { mPos.SetY(y); } void SetZ(T z) { mPos.SetZ(z); } @@ -87,7 +87,7 @@ class BasicXYZVHit : public BaseHit } void SetPos(math_utils::Point3D const& p) { mPos = p; } - ClassDefNV(BasicXYZVHit, 1); + ClassDefNV(BasicXYZVHit, 2); }; // Class for a hit containing energy loss as hit value diff --git a/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h b/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h index b718b2d5eb804..0dc3806e52cf2 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h +++ b/DataFormats/simulation/include/SimulationDataFormat/DigitizationContext.h @@ -141,6 +141,12 @@ class DigitizationContext // have to have same vertex, as well as event ids associated to same collision. void sampleInteractionVertices(o2::dataformats::MeanVertexObject const& v); + // Function allowing to inject interaction vertixes from the outside. + // Useful when this is given from data for instance. The vertex vector needs to be of same + // size as the interaction record. + // Returns 0 if success. 1 if there is a problem. + int setInteractionVertices(std::vector> const& vertices); + // helper functions to save and load a context void saveToFile(std::string_view filename) const; diff --git a/DataFormats/simulation/include/SimulationDataFormat/InteractionSampler.h b/DataFormats/simulation/include/SimulationDataFormat/InteractionSampler.h index d2ccec147cc4f..47dd4f5e4652d 100644 --- a/DataFormats/simulation/include/SimulationDataFormat/InteractionSampler.h +++ b/DataFormats/simulation/include/SimulationDataFormat/InteractionSampler.h @@ -22,6 +22,7 @@ #include "CommonDataFormat/BunchFilling.h" #include "CommonConstants/LHCConstants.h" #include "MathUtils/RandomRing.h" +#include namespace o2 { @@ -130,6 +131,30 @@ class FixedSkipBC_InteractionSampler : public InteractionSampler ClassDef(FixedSkipBC_InteractionSampler, 1); }; +// A version of the interaction sampler which can sample according to non-uniform mu(bc) as +// observed during data taking. +class NonUniformMuInteractionSampler : public InteractionSampler +{ + public: + NonUniformMuInteractionSampler() : InteractionSampler() { mBCIntensityScales.resize(o2::constants::lhc::LHCMaxBunches, 1); } + bool setBCIntensityScales(const std::vector& scales_from_vector); + bool setBCIntensityScales(const TH1F& scales_from_histo); // initialize scales + + // helper function to determine the scales from a histogram (count from event selection analysis) + std::vector determineBCIntensityScalesFromHistogram(const TH1F& scales_from_histo); + + const std::vector& getBCIntensityScales() const { return mBCIntensityScales; } + + protected: + int simulateInteractingBC() override; + int getBCJump() const; + + private: + // non-uniformity + std::vector mBCIntensityScales; + ClassDef(NonUniformMuInteractionSampler, 1); +}; + } // namespace steer } // 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/DataFormats/simulation/include/SimulationDataFormat/ProcessingEventInfo.h b/DataFormats/simulation/include/SimulationDataFormat/ProcessingEventInfo.h deleted file mode 100644 index 150a8272c7714..0000000000000 --- a/DataFormats/simulation/include/SimulationDataFormat/ProcessingEventInfo.h +++ /dev/null @@ -1,36 +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 ProcessingEventInfo.h -/// \brief Encapsulated meta information about current event being processed by FairRoot (analysis) tasks -/// \author Sandro Wenzel - -#ifndef ALICEO2_DATA_EVENTINFO_H_ -#define ALICEO2_DATA_EVENTINFO_H_ - -namespace o2 -{ - -// A class encapsulating meta information about events being process -// and the data being sent by run classes such as FairRunAna. -// Can be send to processing tasks for usage so that they do no longer -// need to access the FairRootManager directly. -struct ProcessingEventInfo { - double eventTime; //! time of the current event - int eventNumber; //! the current entry - int sourceNumber; //! the current source number - bool numberSources; //! number of sources - // can be extended further -}; - -} // namespace o2 - -#endif diff --git a/DataFormats/simulation/src/DigitizationContext.cxx b/DataFormats/simulation/src/DigitizationContext.cxx index dc3c560a1485b..79e36aa9fa48b 100644 --- a/DataFormats/simulation/src/DigitizationContext.cxx +++ b/DataFormats/simulation/src/DigitizationContext.cxx @@ -577,7 +577,7 @@ void DigitizationContext::applyMaxCollisionFilter(std::vector(tf_indices) = indices_old_to_new[lastindex]; // end; } else { - std::get<1>(tf_indices) = newrecords.size(); // end; + std::get<1>(tf_indices) = newrecords.size() - 1; // end; -1 since index inclusif } if (indices_old_to_new.find(previndex) != indices_old_to_new.end()) { std::get<2>(tf_indices) = indices_old_to_new[previndex]; // previous or "early" index @@ -591,11 +591,6 @@ void DigitizationContext::applyMaxCollisionFilter(std::vector> DigitizationContext::calcTimeframeIndices(long startOrbit, long orbitsPerTF, double orbitsEarly) const { auto timeframeindices = getTimeFrameBoundaries(mEventRecords, startOrbit, orbitsPerTF, orbitsEarly); - LOG(info) << "Fixed " << timeframeindices.size() << " timeframes "; - for (auto p : timeframeindices) { - LOG(info) << std::get<0>(p) << " " << std::get<1>(p) << " " << std::get<2>(p); - } - return timeframeindices; } @@ -635,6 +630,17 @@ struct pair_hash { }; } // namespace +int DigitizationContext::setInteractionVertices(std::vector> const& external_vertices) +{ + if (external_vertices.size() != mEventRecords.size()) { + LOG(error) << "Size mismatch with event record"; + return 1; + } + mInteractionVertices.clear(); + std::copy(external_vertices.begin(), external_vertices.end(), std::back_inserter(mInteractionVertices)); + return 0; +} + void DigitizationContext::sampleInteractionVertices(o2::dataformats::MeanVertexObject const& meanv) { // mapping of source x event --> index into mInteractionVertices @@ -693,11 +699,12 @@ DigitizationContext DigitizationContext::extractSingleTimeframe(int timeframeid, r.mSimPrefixes = mSimPrefixes; r.mMuBC = mMuBC; r.mBCFilling = mBCFilling; + r.mDigitizerInteractionRate = mDigitizerInteractionRate; try { auto tf_ranges = timeframeindices.at(timeframeid); auto startindex = std::get<0>(tf_ranges); - auto endindex = std::get<1>(tf_ranges); + auto endindex = std::get<1>(tf_ranges) + 1; auto earlyindex = std::get<2>(tf_ranges); if (earlyindex >= 0) { diff --git a/DataFormats/simulation/src/InteractionSampler.cxx b/DataFormats/simulation/src/InteractionSampler.cxx index 5e14e22e5f8db..f3ece5c51f90b 100644 --- a/DataFormats/simulation/src/InteractionSampler.cxx +++ b/DataFormats/simulation/src/InteractionSampler.cxx @@ -115,8 +115,8 @@ const o2::InteractionTimeRecord& InteractionSampler::generateCollisionTime() int InteractionSampler::simulateInteractingBC() { // Returns number of collisions assigned to selected BC - nextCollidingBC(mBCJumpGenerator.getNextValue()); + // once BC is decided, enforce at least one interaction int ncoll = mNCollBCGenerator.getNextValue(); @@ -162,3 +162,101 @@ void InteractionSampler::setBunchFilling(const std::string& bcFillingFile) mBCFilling = *bc; delete bc; } + +// ________________________________________________ +bool NonUniformMuInteractionSampler::setBCIntensityScales(const std::vector& scales_from_vector) +{ + // Sets the intensity scales per bunch crossing index + // The length of this vector needs to be compatible with the bunch filling chosen + mBCIntensityScales = scales_from_vector; + + if (scales_from_vector.size() != mInteractingBCs.size()) { + LOG(error) << "Scaling factors and bunch filling scheme are not compatible. Not doing anything"; + return false; + } + + float sum = 0.; + for (auto v : mBCIntensityScales) { + sum += std::abs(v); + } + if (sum == 0) { + LOGP(warn, "total intensity is 0, assuming uniform"); + for (auto& v : mBCIntensityScales) { + v = 1.f; + } + } else { // normalize + float norm = mBCIntensityScales.size() / sum; + for (auto& v : mBCIntensityScales) { + v = std::abs(v) * norm; + } + } + return false; +} + +// ________________________________________________ + +bool NonUniformMuInteractionSampler::setBCIntensityScales(const TH1F& hist) +{ + return setBCIntensityScales(determineBCIntensityScalesFromHistogram(hist)); +} + +std::vector NonUniformMuInteractionSampler::determineBCIntensityScalesFromHistogram(const TH1F& hist) +{ + if (mInteractingBCs.size() == 0) { + LOG(error) << " Initialize bunch crossing scheme before assigning scales"; + } + std::vector scales; + // we go through the BCs and query the count from histogram + for (auto bc : mInteractingBCs) { + scales.push_back(hist.GetBinContent(bc + 1)); + } + return scales; +} + +int NonUniformMuInteractionSampler::getBCJump() const +{ + auto muFunc = [this](int bc_position) { + return mBCIntensityScales[bc_position % mInteractingBCs.size()] * mMuBC; + }; + + double U = gRandom->Rndm(); // uniform (0,1) + double T = -std::log(1.0 - U); // threshold + double sumMu = 0.0; + int offset = 0; + auto bcStart = mCurrBCIdx; // the current bc + + while (sumMu < T) { + auto mu_here = muFunc(bcStart + offset); // mu at next BC + sumMu += mu_here; + if (sumMu >= T) { + break; // found BC with at least one collision + } + ++offset; + } + return offset; +} + +int NonUniformMuInteractionSampler::simulateInteractingBC() +{ + nextCollidingBC(getBCJump()); + + auto muFunc = [this](int bc_position) { + return mBCIntensityScales[bc_position % mInteractingBCs.size()] * mMuBC; + }; + + // now sample number of collisions in chosenBC, conditioned >=1: + double mu_chosen = muFunc(mCurrBCIdx); // or does it need to be mCurrBCIdx + int ncoll = 0; + do { + ncoll = gRandom->Poisson(mu_chosen); + } while (ncoll == 0); + + // assign random time withing a bunch + for (int i = ncoll; i--;) { + mTimeInBC.push_back(mCollTimeGenerator.getNextValue()); + } + if (ncoll > 1) { // sort in DECREASING time order (we are reading vector from the end) + std::sort(mTimeInBC.begin(), mTimeInBC.end(), [](const float a, const float b) { return a > b; }); + } + return ncoll; +} \ No newline at end of file diff --git a/DataFormats/simulation/src/SimulationDataLinkDef.h b/DataFormats/simulation/src/SimulationDataLinkDef.h index 15abe9d50390f..8f74bd757e791 100644 --- a/DataFormats/simulation/src/SimulationDataLinkDef.h +++ b/DataFormats/simulation/src/SimulationDataLinkDef.h @@ -25,6 +25,7 @@ #pragma link C++ class o2::steer::InteractionSampler + ; #pragma link C++ class o2::steer::FixedSkipBC_InteractionSampler + ; +#pragma link C++ class o2::steer::NonUniformMuInteractionSampler + ; #pragma link C++ class o2::sim::StackParam + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::sim::StackParam> + ; #pragma link C++ class o2::MCTrackT < double> + ; diff --git a/DataFormats/simulation/test/testInteractionSampler.cxx b/DataFormats/simulation/test/testInteractionSampler.cxx new file mode 100644 index 0000000000000..b1b3691884ccf --- /dev/null +++ b/DataFormats/simulation/test/testInteractionSampler.cxx @@ -0,0 +1,76 @@ +// 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 BOOST_TEST_MODULE Test InteractionSampler class +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include "SimulationDataFormat/InteractionSampler.h" +#include "CCDB/BasicCCDBManager.h" +#include "DataFormatsParameters/AggregatedRunInfo.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "TFile.h" +#include "TGrid.h" +#include + +namespace o2 +{ + +BOOST_AUTO_TEST_CASE(NonUniformSampler) +{ + auto run_number = 559827; + TGrid::Connect("alien"); + if (gGrid) { + auto runInfo = o2::parameters::AggregatedRunInfo::buildAggregatedRunInfo(o2::ccdb::BasicCCDBManager::instance(), run_number); + + o2::steer::NonUniformMuInteractionSampler sampler; + sampler.setBunchFilling(runInfo.grpLHC->getBunchFilling()); + + // the test distribution provided by Igor Altsybeev + auto distr_file = TFile::Open("alien:///alice/cern.ch/user/s/swenzel/AliceO2_TestData/NBcVTX_559827/hBcTVX_data_PbPb_24ar_559827.root"); + + // + if (distr_file && !distr_file->IsZombie()) { + auto hist = distr_file->Get("hBcTVX"); + if (hist) { + sampler.init(); + sampler.setBCIntensityScales(*hist); + + // sample into a vector of a certain size + std::vector samples; + + int N = 100000; + samples.resize(N); + + sampler.generateCollisionTimes(samples); + + // fill an output histogram + auto output_hist = (TH1F*)hist->Clone("h2"); // make a full copy + output_hist->Reset(); + + for (const auto& sample : samples) { + output_hist->Fill(sample.bc); + } + + // Write out + auto fout = TFile::Open("NBCVTX_out.root", "RECREATE"); + fout->WriteObject(output_hist, "NBcVTX"); + fout->Close(); + + // compare mean values of original and newly sampled hist + BOOST_CHECK_CLOSE(hist->GetMean(), output_hist->GetMean(), 0.5); + } + } + } +} + +} // namespace o2 diff --git a/Detectors/AOD/CMakeLists.txt b/Detectors/AOD/CMakeLists.txt index 7ab36d260e480..827b23b3e4cdd 100644 --- a/Detectors/AOD/CMakeLists.txt +++ b/Detectors/AOD/CMakeLists.txt @@ -19,6 +19,7 @@ target_link_libraries( O2::FDDWorkflow O2::FV0Workflow O2::Framework + O2::FrameworkAnalysisSupport O2::GlobalTracking O2::GlobalTrackingWorkflow O2::ITSMFTWorkflow @@ -75,6 +76,7 @@ o2_add_executable( O2::DataFormatsFT0 O2::Steer O2::ZDCBase + O2::FrameworkAnalysisSupport nlohmann_json::nlohmann_json ) diff --git a/Detectors/AOD/include/AODProducerWorkflow/AODMcProducerHelpers.h b/Detectors/AOD/include/AODProducerWorkflow/AODMcProducerHelpers.h index 42431d19cb210..5e9cd445b576b 100644 --- a/Detectors/AOD/include/AODProducerWorkflow/AODMcProducerHelpers.h +++ b/Detectors/AOD/include/AODProducerWorkflow/AODMcProducerHelpers.h @@ -315,7 +315,8 @@ uint32_t updateParticles(const ParticleCursor& cursor, bool background = false, uint32_t weightMask = 0xFFFFFFF0, uint32_t momentumMask = 0xFFFFFFF0, - uint32_t positionMask = 0xFFFFFFF0); + uint32_t positionMask = 0xFFFFFFF0, + bool signalFilter = false); } // namespace o2::aodmchelpers #endif /* O2_AODMCPRODUCER_HELPERS */ diff --git a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h index 62b99e98f990d..02f1b2582d74b 100644 --- a/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h +++ b/Detectors/AOD/include/AODProducerWorkflow/AODProducerWorkflowSpec.h @@ -19,6 +19,10 @@ #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsPHOS/Cell.h" #include "DataFormatsTRD/TrackTRD.h" +#include "TRDBase/PadCalibrationsAliases.h" +#include "DataFormatsTRD/NoiseCalibration.h" +#include "DataFormatsTRD/CalGain.h" +#include "DataFormatsTRD/Constants.h" #include "DetectorsBase/GRPGeomHelper.h" #include "DetectorsBase/Propagator.h" #include "Framework/DataProcessorSpec.h" @@ -44,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 @@ -215,7 +227,7 @@ enum struct AODProducerStreamerFlags : uint8_t { class AODProducerWorkflowDPL : public Task { public: - AODProducerWorkflowDPL(GID::mask_t src, std::shared_ptr dataRequest, std::shared_ptr gr, bool enableSV, bool useMC = true, bool enableFITextra = false) : mUseMC(useMC), mEnableSV(enableSV), mEnableFITextra(enableFITextra), mInputSources(src), mDataRequest(dataRequest), mGGCCDBRequest(gr) {} + AODProducerWorkflowDPL(GID::mask_t src, std::shared_ptr dataRequest, std::shared_ptr gr, bool enableSV, bool useMC = true, bool enableFITextra = false, bool enableTRDextra = false) : mUseMC(useMC), mEnableSV(enableSV), mEnableFITextra(enableFITextra), mEnableTRDextra(enableTRDextra), mInputSources(src), mDataRequest(dataRequest), mGGCCDBRequest(gr) {} ~AODProducerWorkflowDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -237,6 +249,9 @@ 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}; int64_t mTrackQCNTrCut{4}; float mTrackQCDCAxy{3.}; @@ -248,6 +263,10 @@ class AODProducerWorkflowDPL : public Task o2::dataformats::MeanVertexObject mVtx; float mMaxPropXiu{5.0f}; // max X_IU for which track is to be propagated if mPropTracks is true. (other option: o2::constants::geom::XTPCInnerRef + 0.1f) + const o2::trd::LocalGainFactor* mTRDLocalGain; // TRD local gain factors from krypton calibration + const o2::trd::CalGain* mTRDGainCalib; // TRD time-dependent gain calib at chamber level + const o2::trd::NoiseStatusMCM* mTRDNoiseMap; // TRD noise map + std::unordered_set mGIDUsedBySVtx; std::unordered_set mGIDUsedByStr; @@ -256,8 +275,10 @@ class AODProducerWorkflowDPL : public Task int mNThreads = 1; bool mUseMC = true; + bool mUseSigFiltMC = false; // enable signal filtering for MC with embedding bool mEnableSV = true; // enable secondary vertices bool mEnableFITextra = false; + bool mEnableTRDextra = false; bool mFieldON = false; const float cSpeed = 0.029979246f; // speed of light in TOF units @@ -271,10 +292,12 @@ 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}; uint64_t mEMCALTrgClassMask = 0; + size_t mCurrentTRDTrigID = 0; // current index of the TRD trigger record, to speed up search // unordered map connects global indices and table indices of barrel tracks std::unordered_map mGIDToTableID; @@ -522,8 +545,11 @@ class AODProducerWorkflowDPL : public Task template void addToTracksQATable(TracksQACursorType& tracksQACursor, TrackQA& trackQAInfoHolder); - template - void addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, + template + void addToTRDsExtra(const o2::globaltracking::RecoContainer& recoData, TRDsExtraCursorType& trdExtraCursor, const GIndex& trkIdx, int trkTableIdx); + + 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); @@ -541,7 +567,7 @@ class AODProducerWorkflowDPL : public Task // helper for track tables // * fills tables collision by collision // * interaction time is for TOF information - template void fillTrackTablesPerCollision(int collisionID, @@ -553,6 +579,7 @@ class AODProducerWorkflowDPL : public Task TracksCovCursorType& tracksCovCursor, TracksExtraCursorType& tracksExtraCursor, TracksQACursorType& tracksQACursor, + TRDsExtraCursorType& trdsExtraCursor, AmbigTracksCursorType& ambigTracksCursor, MFTTracksCursorType& mftTracksCursor, MFTTracksCovCursorType& mftTracksCovCursor, @@ -643,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, @@ -677,7 +704,7 @@ class AODProducerWorkflowDPL : public Task }; /// create a processor spec -framework::DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, bool enableST, bool useMC, bool CTPConfigPerRun, bool enableFITextra); +framework::DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, bool enableST, bool useMC, bool CTPConfigPerRun, bool enableFITextra, bool enableTRDextra); // helper interface for calo cells to "befriend" emcal and phos cells class CellHelper diff --git a/Detectors/AOD/src/AODMcProducerHelpers.cxx b/Detectors/AOD/src/AODMcProducerHelpers.cxx index 1a01f103dcfdb..a7093e0048c25 100644 --- a/Detectors/AOD/src/AODMcProducerHelpers.cxx +++ b/Detectors/AOD/src/AODMcProducerHelpers.cxx @@ -305,7 +305,8 @@ uint32_t updateParticles(const ParticleCursor& cursor, bool background, uint32_t weightMask, uint32_t momentumMask, - uint32_t positionMask) + uint32_t positionMask, + bool signalFilter) { using o2::mcutils::MCTrackNavigator; using namespace o2::aod::mcparticle::enums; @@ -354,6 +355,9 @@ uint32_t updateParticles(const ParticleCursor& cursor, continue; } } + if (background && signalFilter) { + continue; + } // Store this particle. We mark that putting a 1 in the // `toStore` mapping. This will later on be updated with the diff --git a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx index 8247eb3d870c0..8365628f1644b 100644 --- a/Detectors/AOD/src/AODProducerWorkflowSpec.cxx +++ b/Detectors/AOD/src/AODProducerWorkflowSpec.cxx @@ -33,7 +33,6 @@ #include "DataFormatsPHOS/TriggerRecord.h" #include "DataFormatsPHOS/EventHandler.h" #include "DataFormatsTPC/TrackTPC.h" -#include "DataFormatsTRD/TriggerRecord.h" #include "DataFormatsZDC/BCRecData.h" #include "DataFormatsZDC/ZDCEnergy.h" #include "DataFormatsZDC/ZDCTDCData.h" @@ -45,6 +44,9 @@ #include "CommonDataFormat/InteractionRecord.h" #include "DataFormatsTRD/TrackTRD.h" #include "DataFormatsTRD/TrackTriggerRecord.h" +#include "DataFormatsTRD/CalibratedTracklet.h" +#include "DataFormatsTRD/TriggerRecord.h" +#include "DataFormatsTRD/Tracklet64.h" #include "DataFormatsGlobalTracking/RecoContainer.h" #include "Framework/AnalysisDataModel.h" #include "Framework/ConfigParamRegistry.h" @@ -58,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" @@ -363,10 +365,10 @@ void AODProducerWorkflowDPL::addToTracksQATable(TracksQACursorType& tracksQACurs { tracksQACursor( trackQAInfoHolder.trackID, - truncateFloatFraction(trackQAInfoHolder.tpcTime0, mTPCTime0), + mTrackQCRetainOnlydEdx ? 0.0f : truncateFloatFraction(trackQAInfoHolder.tpcTime0, mTPCTime0), truncateFloatFraction(trackQAInfoHolder.tpcdEdxNorm, mTrackSignal), - trackQAInfoHolder.tpcdcaR, - trackQAInfoHolder.tpcdcaZ, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.tpcdcaR, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.tpcdcaZ, trackQAInfoHolder.tpcClusterByteMask, trackQAInfoHolder.tpcdEdxMax0R, trackQAInfoHolder.tpcdEdxMax1R, @@ -376,22 +378,137 @@ void AODProducerWorkflowDPL::addToTracksQATable(TracksQACursorType& tracksQACurs trackQAInfoHolder.tpcdEdxTot1R, trackQAInfoHolder.tpcdEdxTot2R, trackQAInfoHolder.tpcdEdxTot3R, - trackQAInfoHolder.dRefContY, - trackQAInfoHolder.dRefContZ, - trackQAInfoHolder.dRefContSnp, - trackQAInfoHolder.dRefContTgl, - trackQAInfoHolder.dRefContQ2Pt, - trackQAInfoHolder.dRefGloY, - trackQAInfoHolder.dRefGloZ, - trackQAInfoHolder.dRefGloSnp, - trackQAInfoHolder.dRefGloTgl, - trackQAInfoHolder.dRefGloQ2Pt, - trackQAInfoHolder.dTofdX, - trackQAInfoHolder.dTofdZ); + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefContY, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefContZ, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefContSnp, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefContTgl, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefContQ2Pt, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefGloY, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefGloZ, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefGloSnp, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefGloTgl, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dRefGloQ2Pt, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dTofdX, + mTrackQCRetainOnlydEdx ? std::numeric_limits::min() : trackQAInfoHolder.dTofdZ); } -template -void AODProducerWorkflowDPL::addToMFTTracksTable(mftTracksCursorType& mftTracksCursor, AmbigMFTTracksCursorType& ambigMFTTracksCursor, +template +void AODProducerWorkflowDPL::addToTRDsExtra(const o2::globaltracking::RecoContainer& recoData, TRDsExtraCursorType& trdExtraCursor, const GIndex& trkIdx, int trkTableIdx) +{ + int q0s[6] = {-1}, q1s[6] = {-1}, q2s[6] = {-1}; + float q0sCor[6] = {-1}, q1sCor[6] = {-1}, q2sCor[6] = {-1}; + float ttgls[6] = {-999}, tphis[6] = {-999}; + + auto contributorsGID = recoData.getSingleDetectorRefs(trkIdx); + if (!contributorsGID[GIndex::Source::TRD].isIndexSet()) { // should be redunant + return; + } + const auto& trk = recoData.getTrack(contributorsGID[GIndex::Source::TRD]); + o2::track::TrackPar trkC{contributorsGID[GIndex::Source::ITSTPC].isIndexSet() ? recoData.getTPCITSTrack(contributorsGID[GIndex::Source::ITSTPC]).getParamOut() : recoData.getTPCTrack(contributorsGID[GIndex::Source::TPC]).getParamOut()}; + const auto& trklets = recoData.getTRDTracklets(); + const auto& ctrklets = recoData.getTRDCalibratedTracklets(); + for (int iLay{0}; iLay < 6; ++iLay) { + q0s[iLay] = q1s[iLay] = q2s[iLay] = -1; + q0sCor[iLay] = q1sCor[iLay] = q2sCor[iLay] = -1; + tphis[iLay] = ttgls[iLay] = -999; + auto trkltId = trk.getTrackletIndex(iLay); + if (trkltId < 0) { + continue; + } + const auto& tracklet = trklets[trkltId]; + if (mTRDNoiseMap->isTrackletFromNoisyMCM(tracklet)) { + continue; + } + // we need to propagate into TRD local system + int trkltDet = tracklet.getDetector(); + int trkltSec = trkltDet / 30; + if (trkltSec != o2::math_utils::angle2Sector(trkC.getAlpha())) { + if (!trkC.rotate(o2::math_utils::sector2Angle(trkltSec))) { + break; + } + } + if (!o2::base::Propagator::Instance()->PropagateToXBxByBz(trkC, ctrklets[trkltId].getX(), o2::base::Propagator::MAX_SIN_PHI, o2::base::Propagator::MAX_STEP, mMatCorr)) { + break; + } + + auto tphi = trkC.getSnp() / std::sqrt((1.f - trkC.getSnp()) * (1.f + trkC.getSnp())); + auto trackletLength = std::sqrt(1.f + tphi * tphi + trkC.getTgl() * trkC.getTgl()); + float cor = mTRDLocalGain->getValue(tracklet.getHCID() / 2, tracklet.getPadCol(), tracklet.getPadRow()) * mTRDGainCalib->getMPVdEdx(tracklet.getDetector()) / o2::trd::constants::MPVDEDXDEFAULT * trackletLength; + q0s[iLay] = tracklet.getQ0(); + q1s[iLay] = tracklet.getQ1(); + q2s[iLay] = tracklet.getQ2(); + q0sCor[iLay] = (float)tracklet.getQ0() / cor; + q1sCor[iLay] = (float)tracklet.getQ1() / cor; + q2sCor[iLay] = (float)tracklet.getQ2() / cor; + ttgls[iLay] = trkC.getTgl(); + tphis[iLay] = tphi; + + // z-row merging, we want to merge only with tracklets from the same trigger record + if (trk.getIsCrossingNeighbor(iLay) && trk.getHasNeighbor()) { + // find the trigger the tracklet belongs to + auto trigsTRD = recoData.getTRDTriggerRecords(); + size_t trdSelID = -1; + + const auto& trig = trigsTRD[mCurrentTRDTrigID]; + bool foundTRDTrigger = false; + // first check current trigger + if (trkltId >= trig.getFirstTracklet() && trkltId < trig.getFirstTracklet() + trig.getNumberOfTracklets()) { + trdSelID = mCurrentTRDTrigID; + foundTRDTrigger = true; + } else { + // then check next trigger + if (mCurrentTRDTrigID < trigsTRD.size() - 1) { + const auto& trig = trigsTRD[mCurrentTRDTrigID + 1]; + if (trkltId >= trig.getFirstTracklet() && trkltId < trig.getFirstTracklet() + trig.getNumberOfTracklets()) { + trdSelID = mCurrentTRDTrigID + 1; + foundTRDTrigger = true; + } + } + } + + size_t low = 0, up = trigsTRD.size() - 1; + + // otherwise binary search + while (low <= up && !foundTRDTrigger) { + trdSelID = low + std::floor((up - low) / 2); + const auto& trig = trigsTRD[trdSelID]; + if (trig.getFirstTracklet() > trkltId) { + up = trdSelID - 1; + } else { + if (trig.getFirstTracklet() + trig.getNumberOfTracklets() <= trkltId) { + low = trdSelID + 1; + } else { + foundTRDTrigger = true; + } + } + } + //------------------- + mCurrentTRDTrigID = trdSelID; + const auto& trigSel = trigsTRD[trdSelID]; + + // loop on other tracklets from the same trigger record + for (const auto& trklt : trklets.subspan(trigSel.getFirstTracklet(), trigSel.getNumberOfTracklets())) { + if (tracklet.getTrackletWord() == trklt.getTrackletWord() || tracklet.getDetector() != trklt.getDetector()) { + continue; + } + if (std::abs(tracklet.getPadCol() - trklt.getPadCol()) <= 1 && std::abs(tracklet.getPadRow() - trklt.getPadRow()) == 1) { + cor = mTRDLocalGain->getValue(trklt.getHCID() / 2, trklt.getPadCol(), trklt.getPadRow()) * mTRDGainCalib->getMPVdEdx(tracklet.getDetector()) / o2::trd::constants::MPVDEDXDEFAULT * trackletLength; + q0s[iLay] += trklt.getQ0(); + q1s[iLay] += trklt.getQ1(); + q2s[iLay] += trklt.getQ2(); + q0sCor[iLay] += (float)trklt.getQ0() / cor; + q1sCor[iLay] += (float)trklt.getQ1() / cor; + q2sCor[iLay] += (float)trklt.getQ2() / cor; + } + } + } + } + + trdExtraCursor(trkTableIdx, q0s, q1s, q2s, q0sCor, q1sCor, q2sCor, ttgls, tphis); +} + +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) { @@ -426,12 +543,35 @@ 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); } } - -template void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, @@ -443,6 +583,7 @@ void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, TracksCovCursorType& tracksCovCursor, TracksExtraCursorType& tracksExtraCursor, TracksQACursorType& tracksQACursor, + TRDsExtraCursor& trdsExtraCursor, AmbigTracksCursorType& ambigTracksCursor, MFTTracksCursorType& mftTracksCursor, MFTTracksCovCursorType& mftTracksCovCursor, @@ -462,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 { @@ -480,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 @@ -499,7 +643,7 @@ void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, float weight = 0; static std::uniform_real_distribution<> distr(0., 1.); - bool writeQAData = o2::math_utils::Tsallis::downsampleTsallisCharged(data.getTrackParam(trackIndex).getPt(), mTrackQCFraction, mSqrtS, weight, distr(mGenerator)); + bool writeQAData = o2::math_utils::Tsallis::downsampleTsallisCharged(data.getTrackParam(trackIndex).getPt(), mTrackQCFraction, mSqrtS, weight, distr(mGenerator)) || ((src != GIndex::TPC || mGIDUsedBySVtx.find(trackIndex) != mGIDUsedBySVtx.end() || mGIDUsedByStr.find(trackIndex) != mGIDUsedByStr.end()) && mTrackQCKeepGlobalTracks); auto extraInfoHolder = processBarrelTrack(collisionID, collisionBC, trackIndex, data, bcsMap); if (writeQAData) { @@ -553,7 +697,9 @@ void AODProducerWorkflowDPL::fillTrackTablesPerCollision(int collisionID, addToTracksTable(tracksCursor, tracksCovCursor, trOrig, collisionID, aod::track::TrackIU); } addToTracksExtraTable(tracksExtraCursor, extraInfoHolder); - + if (mEnableTRDextra && trackIndex.includesDet(GIndex::Source::TRD)) { + addToTRDsExtra(data, trdsExtraCursor, trackIndex, mTableTrID); + } // collecting table indices of barrel tracks for V0s table if (extraInfoHolder.bcSlice[0] >= 0 && collisionID < 0) { ambigTracksCursor(mTableTrID, extraInfoHolder.bcSlice); @@ -810,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; @@ -947,13 +1095,17 @@ void clearMCKeepStore(std::vector>>& st } // helper function to add a particle/track to the MC keep store -void keepMCParticle(std::vector>>& store, int source, int event, int track, int value = 1) +void keepMCParticle(std::vector>>& store, int source, int event, int track, int value = 1, bool useSigFilt = false) { if (track < 0) { LOG(warn) << "trackID is smaller than 0. Neglecting"; return; } - store[source][event][track] = value; + if (useSigFilt && source == 0) { + store[source][event][track] = -1; + } else { + store[source][event][track] = value; + } } void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& mcReader, @@ -961,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++; @@ -982,7 +1134,7 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& if (!mcLabel.isValid()) { return; } - keepMCParticle(mToStore, mcLabel.getSourceID(), mcLabel.getEventID(), mcLabel.getTrackID()); + keepMCParticle(mToStore, mcLabel.getSourceID(), mcLabel.getEventID(), mcLabel.getTrackID(), 1, mUseSigFiltMC); }; // mark reconstructed MC particles to store them into the table @@ -997,7 +1149,7 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& if (!mcTruth.isValid()) { continue; } - keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID()); + keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID(), 1, mUseSigFiltMC); // treating contributors of global tracks auto contributorsGID = data.getSingleDetectorRefs(trackIndex); if (contributorsGID[GIndex::Source::TPC].isIndexSet()) { @@ -1012,7 +1164,7 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& if (!mcLabel.isValid()) { continue; } - keepMCParticle(mToStore, mcLabel.getSourceID(), mcLabel.getEventID(), mcLabel.getTrackID()); + keepMCParticle(mToStore, mcLabel.getSourceID(), mcLabel.getEventID(), mcLabel.getTrackID(), 1, mUseSigFiltMC); } } } @@ -1026,7 +1178,7 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& if (!mcTruth.isValid()) { continue; } - keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID()); + keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID(), 1, mUseSigFiltMC); } } if (mInputSources[GIndex::PHS]) { @@ -1035,7 +1187,7 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& if (!mcTruth.isValid()) { continue; } - keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID()); + keepMCParticle(mToStore, mcTruth.getSourceID(), mcTruth.getEventID(), mcTruth.getTrackID(), 1, mUseSigFiltMC); } } using namespace aodmchelpers; @@ -1043,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]; @@ -1059,7 +1211,8 @@ void AODProducerWorkflowDPL::fillMCParticlesTable(o2::steer::MCKinematicsReader& source == 0, // background mMcParticleW, mMcParticleMom, - mMcParticlePos); + mMcParticlePos, + mUseSigFiltMC); mcReader.releaseTracksForSourceAndEvent(source, event); } @@ -1126,7 +1279,7 @@ void AODProducerWorkflowDPL::fillMCTrackLabelsTable(MCTrackLabelCursorType& mcTr if (!needToStore(mGIDToTableID)) { continue; } - if (mcTruth.isValid()) { // if not set, -1 will be stored + if (mcTruth.isValid()) { // if not set, -1 will be stored labelHolder.labelID = (mToStore[mcTruth.getSourceID()][mcTruth.getEventID()])[mcTruth.getTrackID()]; // defined by TPC if it contributes, otherwise: by ITS if (mcTruth.isFake()) { labelHolder.labelMask |= (0x1 << 15); @@ -1139,6 +1292,21 @@ void AODProducerWorkflowDPL::fillMCTrackLabelsTable(MCTrackLabelCursorType& mcTr } } } + if (trackIndex.includesDet(DetID::ITS)) { + auto itsGID = data.getITSContributorGID(trackIndex); + auto itsSource = itsGID.getSource(); + if (itsSource == GIndex::ITS) { + auto& itsTrack = data.getITSTrack(itsGID); + for (unsigned int iL = 0; iL < 7; ++iL) { + if (itsTrack.isFakeOnLayer(iL)) { + labelHolder.labelMask |= (0x1 << iL); + } + } + } else if (itsSource == GIndex::ITSAB) { + labelHolder.labelMask |= (data.getTrackMCLabel(itsGID).isFake() << 12); + } + } + } else if (mcTruth.isNoise()) { labelHolder.labelMask |= (0x1 << 14); } @@ -1679,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"); @@ -1690,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) { @@ -1699,6 +1869,8 @@ void AODProducerWorkflowDPL::init(InitContext& ic) LOGP(warn, "Specified non-default empty streamer mask!"); } } + mTrackQCKeepGlobalTracks = ic.options().get("trackqc-keepglobaltracks"); + mTrackQCRetainOnlydEdx = ic.options().get("trackqc-retainonlydedx"); mTrackQCFraction = ic.options().get("trackqc-fraction"); mTrackQCNTrCut = ic.options().get("trackqc-NTrCut"); mTrackQCDCAxy = ic.options().get("trackqc-tpc-dca"); @@ -1728,6 +1900,8 @@ void AODProducerWorkflowDPL::init(InitContext& ic) LOG(info) << "The Run number will be obtained from DPL headers"; } + mUseSigFiltMC = ic.options().get("mc-signal-filt"); + // set no truncation if selected by user if (mTruncate != 1) { LOG(info) << "Truncation is not used!"; @@ -1910,6 +2084,12 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) auto cpvClustersCursor = createTableCursor(pc); auto originCursor = createTableCursor(pc); + /// Extra tables + o2::framework::Produces trdExtraCursor; + if (mEnableTRDextra) { + trdExtraCursor = createTableCursor(pc); + } + // Declare MC cursors type without adding the output for a table o2::framework::Produces mcColLabelsCursor; o2::framework::Produces mcCollisionsCursor; @@ -2029,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; @@ -2042,6 +2220,28 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) const auto& mcRecords = mcReader->getDigitizationContext()->getEventRecords(); const auto& mcParts = mcReader->getDigitizationContext()->getEventParts(); + // if signal filtering enabled, let's check if there are more than one source; otherwise fatalise + if (mUseSigFiltMC) { + std::vector sourceIDs{}; + for (int iCol = 0; iCol < nMCCollisions; iCol++) { + for (auto const& colPart : mcParts[iCol]) { + int sourceID = colPart.sourceID; + if (std::find(sourceIDs.begin(), sourceIDs.end(), sourceID) == sourceIDs.end()) { + sourceIDs.push_back(sourceID); + } + if (sourceIDs.size() > 1) { // we found more than one, exit + break; + } + } + if (sourceIDs.size() > 1) { // we found more than one, exit + break; + } + } + if (sourceIDs.size() <= 1) { + LOGP(fatal, "Signal filtering cannot be enabled without embedding. Please fix the configuration either enabling the embedding, or turning off the signal filtering."); + } + } + // count all parts int totalNParts = 0; for (int iCol = 0; iCol < nMCCollisions; iCol++) { @@ -2083,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}; @@ -2188,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); } } @@ -2248,14 +2478,16 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) } } + mCurrentTRDTrigID = 0; // reinitialize index for TRD trigger record search // filling unassigned tracks first // so that all unassigned tracks are stored in the beginning of the table together auto& trackRef = primVer2TRefs.back(); // references to unassigned tracks are at the end // fixme: interaction time is undefined for unassigned tracks (?) - fillTrackTablesPerCollision(-1, std::uint64_t(-1), trackRef, primVerGIs, recoData, tracksCursor, tracksCovCursor, tracksExtraCursor, tracksQACursor, + fillTrackTablesPerCollision(-1, std::uint64_t(-1), trackRef, primVerGIs, recoData, tracksCursor, tracksCovCursor, tracksExtraCursor, tracksQACursor, trdExtraCursor, ambigTracksCursor, mftTracksCursor, mftTracksCovCursor, ambigMFTTracksCursor, fwdTracksCursor, fwdTracksCovCursor, ambigFwdTracksCursor, fwdTrkClsCursor, bcsMap); + mCurrentTRDTrigID = 0; // reinitialize index for TRD trigger record search // filling collisions and tracks into tables collisionID = 0; collisionsCursor.reserve(primVertices.size()); @@ -2294,7 +2526,7 @@ void AODProducerWorkflowDPL::run(ProcessingContext& pc) auto& trackRef = primVer2TRefs[collisionID]; // passing interaction time in [ps] - fillTrackTablesPerCollision(collisionID, globalBC, trackRef, primVerGIs, recoData, tracksCursor, tracksCovCursor, tracksExtraCursor, tracksQACursor, ambigTracksCursor, + fillTrackTablesPerCollision(collisionID, globalBC, trackRef, primVerGIs, recoData, tracksCursor, tracksCovCursor, tracksExtraCursor, tracksQACursor, trdExtraCursor, ambigTracksCursor, mftTracksCursor, mftTracksCovCursor, ambigMFTTracksCursor, fwdTracksCursor, fwdTracksCovCursor, ambigFwdTracksCursor, fwdTrkClsCursor, bcsMap); collisionID++; @@ -2442,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(); } @@ -2601,6 +2833,12 @@ AODProducerWorkflowDPL::TrackExtraInfo AODProducerWorkflowDPL::processBarrelTrac if (tpcOrig.getdEdx().dEdxTotTPC == 0) { extraInfoHolder.flags |= o2::aod::track::TPCdEdxAlt; } + if (tpcOrig.hasASideClusters()) { + extraInfoHolder.flags |= o2::aod::track::TPCSideA; + } + if (tpcOrig.hasCSideClusters()) { + extraInfoHolder.flags |= o2::aod::track::TPCSideC; + } extraInfoHolder.tpcInnerParam = tpcOrig.getP() / tpcOrig.getAbsCharge(); extraInfoHolder.tpcChi2NCl = tpcOrig.getNClusters() ? tpcOrig.getChi2() / tpcOrig.getNClusters() : 0; extraInfoHolder.tpcSignal = dEdx.dEdxTotTPC; @@ -2959,6 +3197,11 @@ void AODProducerWorkflowDPL::updateTimeDependentParams(ProcessingContext& pc) mFieldON = std::abs(o2::base::Propagator::Instance()->getNominalBz()) > 0.01; pc.inputs().get("ctpconfig"); + if (mEnableTRDextra) { + mTRDLocalGain = pc.inputs().get("trdlocalgainfactors").get(); + mTRDNoiseMap = pc.inputs().get("trdnoisemap").get(); + mTRDGainCalib = pc.inputs().get("trdgaincalib").get(); // time dependent gain + } } if (mPropTracks) { pc.inputs().get("meanvtx"); @@ -3170,7 +3413,7 @@ void AODProducerWorkflowDPL::endOfStream(EndOfStreamContext& /*ec*/) mStreamer.reset(); } -DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, bool enableStrangenessTracking, bool useMC, bool CTPConfigPerRun, bool enableFITextra) +DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, bool enableStrangenessTracking, bool useMC, bool CTPConfigPerRun, bool enableFITextra, bool enableTRDextra) { auto dataRequest = std::make_shared(); dataRequest->inputs.emplace_back("ctpconfig", "CTP", "CTPCONFIG", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/Config", CTPConfigPerRun)); @@ -3261,6 +3504,13 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo OutputSpec{"TFF", "TFFilename"}, OutputSpec{"AMD", "AODMetadataKeys"}, OutputSpec{"AMD", "AODMetadataVals"}}; + /// Extra tables + if (enableTRDextra) { + outputs.push_back(OutputForTable::spec()); + dataRequest->inputs.emplace_back("trdlocalgainfactors", "TRD", "LOCALGAINFACTORS", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/LocalGainFactor")); + dataRequest->inputs.emplace_back("trdnoisemap", "TRD", "NOISEMAP", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/NoiseMapMCM")); + dataRequest->inputs.emplace_back("trdgaincalib", "TRD", "CALGAIN", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/CalGain")); + } if (useMC) { outputs.insert(outputs.end(), @@ -3284,7 +3534,7 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo "aod-producer-workflow", dataRequest->inputs, outputs, - AlgorithmSpec{adaptFromTask(src, dataRequest, ggRequest, enableSV, useMC, enableFITextra)}, + AlgorithmSpec{adaptFromTask(src, dataRequest, ggRequest, enableSV, useMC, enableFITextra, enableTRDextra)}, Options{ ConfigParamSpec{"run-number", VariantType::Int64, -1L, {"The run-number. If left default we try to get it from DPL header."}}, ConfigParamSpec{"aod-timeframe-id", VariantType::Int64, -1L, {"Set timeframe number"}}, @@ -3294,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"}}, @@ -3303,7 +3554,10 @@ 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"}}, ConfigParamSpec{"trackqc-fraction", VariantType::Float, float(0.1), {"Fraction of tracks to QC"}}, ConfigParamSpec{"trackqc-NTrCut", VariantType::Int64, 4L, {"Minimal length of the track - in amount of tracklets"}}, ConfigParamSpec{"trackqc-tpc-dca", VariantType::Float, 3.f, {"Keep TPC standalone track with this DCAxy to the PV"}}, @@ -3311,7 +3565,7 @@ DataProcessorSpec getAODProducerWorkflowSpec(GID::mask_t src, bool enableSV, boo ConfigParamSpec{"trackqc-tpc-pt", VariantType::Float, 0.2f, {"Keep TPC standalone track with this pt"}}, ConfigParamSpec{"with-streamers", VariantType::String, "", {"Bit-mask to steer writing of intermediate streamer files"}}, ConfigParamSpec{"seed", VariantType::Int, 0, {"Set seed for random generator used for sampling (0 (default) means using a random_device)"}}, - }}; + ConfigParamSpec{"mc-signal-filt", VariantType::Bool, false, {"Enable usage of signal filtering (only for MC with embedding)"}}}}; } } // namespace o2::aodproducer diff --git a/Detectors/AOD/src/aod-producer-workflow.cxx b/Detectors/AOD/src/aod-producer-workflow.cxx index 81e178642e403..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; @@ -38,10 +39,12 @@ void customize(std::vector& workflowOptions) {"disable-secondary-vertices", o2::framework::VariantType::Bool, false, {"disable filling secondary vertices"}}, {"disable-strangeness-tracker", o2::framework::VariantType::Bool, false, {"disable filling strangeness tracking"}}, {"enable-FIT-extra", o2::framework::VariantType::Bool, false, {"enable FIT extra output"}}, + {"enable-TRD-extra", o2::framework::VariantType::Bool, false, {"enable TRD extra output"}}, {"info-sources", VariantType::String, std::string{GID::ALL}, {"comma-separated list of sources to use"}}, {"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); } @@ -56,6 +59,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) bool enableST = !configcontext.options().get("disable-strangeness-tracker"); bool ctpcfgperrun = !configcontext.options().get("ctpconfig-run-independent"); bool enableFITextra = configcontext.options().get("enable-FIT-extra"); + bool enableTRDextra = configcontext.options().get("enable-TRD-extra"); GID::mask_t allowedSrc = GID::getSourcesMask("ITS,MFT,MCH,MID,MCH-MID,TPC,TRD,ITS-TPC,TPC-TOF,TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD,TPC-TRD-TOF,ITS-TPC-TRD-TOF,MFT-MCH,FT0,FV0,FDD,ZDC,EMC,CTP,PHS,CPV,HMP"); GID::mask_t src = allowedSrc & GID::getSourcesMask(configcontext.options().get("info-sources")); @@ -66,7 +70,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) } WorkflowSpec specs; - specs.emplace_back(o2::aodproducer::getAODProducerWorkflowSpec(src, enableSV, enableST, useMC, ctpcfgperrun, enableFITextra)); + specs.emplace_back(o2::aodproducer::getAODProducerWorkflowSpec(src, enableSV, enableST, useMC, ctpcfgperrun, enableFITextra, enableTRDextra)); auto srcCls = src & ~(GID::getSourceMask(GID::MCH) | GID::getSourceMask(GID::MID)); // Don't read global MID and MCH clusters (those attached to tracks are always read) auto srcMtc = src; 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 7681380692033..3e42c19f535f8 100644 --- a/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx +++ b/Detectors/Align/Workflow/src/BarrelAlignmentSpec.cxx @@ -36,8 +36,8 @@ #include "TRDBase/TrackletTransformer.h" #include "CommonUtils/TreeStreamRedirector.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" -#include "GPUO2Interface.h" +#include "TPCFastTransformPOD.h" +#include "GPUO2ExternalUser.h" #include "GPUO2InterfaceUtils.h" #include "GPUParam.h" #include "Headers/DataHeader.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,13 +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); - } + : 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; @@ -121,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; @@ -183,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")) { @@ -271,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, @@ -285,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()); } } } @@ -313,9 +300,6 @@ void BarrelAlignmentSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& match return; } - if (mTPCCorrMapsLoader.accountCCDBInputs(matcher, obj)) { - return; - } } void BarrelAlignmentSpec::run(ProcessingContext& pc) @@ -373,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(); @@ -398,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; } } @@ -416,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/AlignConfig.h b/Detectors/Align/include/Align/AlignConfig.h index 91b503c2c923e..e72d436a14e3b 100644 --- a/Detectors/Align/include/Align/AlignConfig.h +++ b/Detectors/Align/include/Align/AlignConfig.h @@ -85,6 +85,7 @@ struct AlignConfig : public o2::conf::ConfigurableParamHelper { float controlFraction = -1.; // fraction for which control output is requested, if negative - only 1st instance of device will write them float MPRecOutFraction = -1.; // compact Millepede2Record fraction, if negative - only 1st instance of device will write them + bool useLinRef = true; // use initial track for lienarization reference point bool MilleOut = true; // Mille output bool KalmanResid = true; // Kalman residuals bool MilleOutBin = true; // text vs binary output for mille data diff --git a/Detectors/Align/include/Align/AlignableDetectorTRD.h b/Detectors/Align/include/Align/AlignableDetectorTRD.h index a73b0f76902d2..4e7577b11055c 100644 --- a/Detectors/Align/include/Align/AlignableDetectorTRD.h +++ b/Detectors/Align/include/Align/AlignableDetectorTRD.h @@ -18,7 +18,7 @@ #define ALIGNABLEDETECTORTRD_H #include "Align/AlignableDetector.h" -#include "TRDBase/RecoParam.h" +#include "GPUTRDRecoParam.h" namespace o2 { @@ -64,7 +64,7 @@ class AlignableDetectorTRD final : public AlignableDetector int processPoints(GIndex gid, int npntCut, bool inv) final; protected: - o2::trd::RecoParam mRecoParam; // parameters required for TRD reconstruction + o2::gpu::GPUTRDRecoParam mRecoParam; // parameters required for TRD reconstruction double mNonRCCorrDzDtgl = 0.; // correction in Z for non-crossing tracklets double mCorrDVT = 0.; // correction to Vdrift*t double mExtraErrRC[2] = {0., 0.}; // extra errors for RC tracklets diff --git a/Detectors/Align/include/Align/AlignmentTrack.h b/Detectors/Align/include/Align/AlignmentTrack.h index ef4552cb9a37d..cb69f11cbf85c 100644 --- a/Detectors/Align/include/Align/AlignmentTrack.h +++ b/Detectors/Align/include/Align/AlignmentTrack.h @@ -39,6 +39,7 @@ class AlignmentTrack : public trackParam_t, public TObject { public: using trackParam_t = o2::track::TrackParametrizationWithError; + using trackPar_t = o2::track::TrackParametrization; using PropagatorD = o2::base::PropagatorD; using MatCorrType = PropagatorD::MatCorrType; using GTrackID = o2::dataformats::GlobalTrackID; @@ -83,9 +84,9 @@ class AlignmentTrack : public trackParam_t, public TObject // template void copyFrom(const o2::track::TrackParametrizationWithError

& trc); - bool propagateToPoint(trackParam_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tLT = nullptr, int signCorr = 0); - bool propagateParamToPoint(trackParam_t& tr, const AlignmentPoint* pnt, double maxStep = 3, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, int signCorr = 0); // param only - bool propagateParamToPoint(trackParam_t* trSet, int nTr, const AlignmentPoint* pnt, double maxStep = 3, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, int signCorr = 0); // params only + bool propagateToPoint(trackParam_t& tr, trackPar_t* linRef, const AlignmentPoint* pnt, double maxStep, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tLT = nullptr, int signCorr = 0); + bool propagateParamToPoint(trackPar_t& tr, const AlignmentPoint* pnt, double maxStep = 3, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, int signCorr = 0); // param only + bool propagateParamToPoint(trackPar_t* trSet, int nTr, const AlignmentPoint* pnt, double maxStep = 3, double maxSnp = 0.95, MatCorrType mt = MatCorrType::USEMatCorrLUT, int signCorr = 0); // params only // bool calcResiduals(const double* params = nullptr); bool calcResidDeriv(double* params = nullptr); @@ -119,23 +120,23 @@ class AlignmentTrack : public trackParam_t, public TObject void imposePtBOff(double pt) { setQ2Pt(1. / pt); } // propagation methods void copyFrom(const trackParam_t* etp); - bool applyMatCorr(trackParam_t& trPar, const double* corrDiag, const AlignmentPoint* pnt); - bool applyMatCorr(trackParam_t* trSet, int ntr, const double* corrDiaf, const AlignmentPoint* pnt); - bool applyMatCorr(trackParam_t& trPar, const double* corrpar); + bool applyMatCorr(trackPar_t& trPar, const double* corrDiag, const AlignmentPoint* pnt); + bool applyMatCorr(trackPar_t* trSet, int ntr, const double* corrDiaf, const AlignmentPoint* pnt); + bool applyMatCorr(trackPar_t& trPar, const double* corrpar); // double getResidual(int dim, int pntID) const { return mResid[dim][pntID]; } const double* getDResDLoc(int dim, int pntID) const { return mDResDLoc[dim].data() + (pntID * mNLocPar); } const double* getDResDGlo(int dim, int id) const { return mDResDGlo[dim].data() + id; } const int* getGloParID() const { return mGloParID.data(); } // - void setParams(trackParam_t& tr, double x, double alp, const double* par, bool add); - void setParams(trackParam_t* trSet, int ntr, double x, double alp, const double* par, bool add); - void setParam(trackParam_t& tr, int par, double val); - void setParam(trackParam_t* trSet, int ntr, int par, double val); - void modParam(trackParam_t& tr, int par, double delta); - void modParam(trackParam_t* trSet, int ntr, int par, double delta); + void setParams(trackPar_t& tr, double x, double alp, const double* par, bool add); + void setParams(trackPar_t* trSet, int ntr, double x, double alp, const double* par, bool add); + void setParam(trackPar_t& tr, int par, double val); + void setParam(trackPar_t* trSet, int ntr, int par, double val); + void modParam(trackPar_t& tr, int par, double delta); + void modParam(trackPar_t* trSet, int ntr, int par, double delta); // - void richardsonDeriv(const trackParam_t* trSet, const double* delta, + void richardsonDeriv(const trackPar_t* trSet, const double* delta, const AlignmentPoint* pnt, double& derY, double& derZ); // const double* getLocPars() const { return mLocPar.data(); } @@ -179,13 +180,14 @@ class AlignmentTrack : public trackParam_t, public TObject std::vector mLocPar; // local parameters array std::vector mGloParID; // IDs of relevant global params private: - bool propagate(trackParam_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr = 0); + bool propagate(trackParam_t& tr, trackPar_t* linRef, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr = 0); + bool propagate(trackPar_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr = 0); // ClassDefOverride(AlignmentTrack, 2) }; //____________________________________________________________________________________________ -inline void AlignmentTrack::setParams(trackParam_t& tr, double x, double alp, const double* par, bool add) +inline void AlignmentTrack::setParams(trackPar_t& tr, double x, double alp, const double* par, bool add) { // set track params const double kDefQ2PtCosm = 1; @@ -205,7 +207,7 @@ inline void AlignmentTrack::setParams(trackParam_t& tr, double x, double alp, co } //____________________________________________________________________________________________ -inline void AlignmentTrack::setParams(trackParam_t* trSet, int ntr, double x, double alp, const double* par, bool add) +inline void AlignmentTrack::setParams(trackPar_t* trSet, int ntr, double x, double alp, const double* par, bool add) { // set parames for multiple tracks (VECTORIZE THIS) if (!add) { // full parameter supplied @@ -224,14 +226,14 @@ inline void AlignmentTrack::setParams(trackParam_t* trSet, int ntr, double x, do } //____________________________________________________________________________________________ -inline void AlignmentTrack::setParam(trackParam_t& tr, int par, double val) +inline void AlignmentTrack::setParam(trackPar_t& tr, int par, double val) { // set track parameter tr.setParam(val, par); } //____________________________________________________________________________________________ -inline void AlignmentTrack::setParam(trackParam_t* trSet, int ntr, int par, double val) +inline void AlignmentTrack::setParam(trackPar_t* trSet, int ntr, int par, double val) { // set parames for multiple tracks (VECTORIZE THIS) for (int i = 0; i < ntr; ++i) { @@ -240,7 +242,7 @@ inline void AlignmentTrack::setParam(trackParam_t* trSet, int ntr, int par, doub } //____________________________________________________________________________________________ -inline void AlignmentTrack::modParam(trackParam_t& tr, int par, double delta) +inline void AlignmentTrack::modParam(trackPar_t& tr, int par, double delta) { // modify track parameter const auto val = tr.getParam(par) + delta; @@ -248,7 +250,7 @@ inline void AlignmentTrack::modParam(trackParam_t& tr, int par, double delta) } //____________________________________________________________________________________________ -inline void AlignmentTrack::modParam(trackParam_t* trSet, int ntr, int par, double delta) +inline void AlignmentTrack::modParam(trackPar_t* trSet, int ntr, int par, double delta) { // modify track parameter (VECTORIZE THOS) for (int i = 0; i < ntr; ++i) { diff --git a/Detectors/Align/include/Align/Controller.h b/Detectors/Align/include/Align/Controller.h index 96ee2e4fcf418..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" @@ -54,7 +54,7 @@ #include #include #include "Align/Mille.h" -// #include "GPUO2Interface.h" +// #include "GPUO2ExternalUser.h" // #include "DataFormatsTPC/WorkflowHelper.h" namespace o2 @@ -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/AlignableDetectorTPC.cxx b/Detectors/Align/src/AlignableDetectorTPC.cxx index b3d2102559974..980ded2d8ff2f 100644 --- a/Detectors/Align/src/AlignableDetectorTPC.cxx +++ b/Detectors/Align/src/AlignableDetectorTPC.cxx @@ -24,7 +24,7 @@ #include "DataFormatsTPC/WorkflowHelper.h" #include #include -#include "GPUO2Interface.h" +#include "GPUO2ExternalUser.h" #include "DataFormatsTPC/WorkflowHelper.h" #include "GPUParam.inc" diff --git a/Detectors/Align/src/AlignableDetectorTRD.cxx b/Detectors/Align/src/AlignableDetectorTRD.cxx index d752553bf6ead..6fe8a60ef90f6 100644 --- a/Detectors/Align/src/AlignableDetectorTRD.cxx +++ b/Detectors/Align/src/AlignableDetectorTRD.cxx @@ -26,6 +26,7 @@ #include "DataFormatsTRD/TrackTRD.h" #include "DataFormatsTRD/Tracklet64.h" #include "DataFormatsTRD/CalibratedTracklet.h" +#include "GPUO2InterfaceConfiguration.h" #include #include @@ -175,10 +176,10 @@ int AlignableDetectorTRD::processPoints(GIndex gid, int npntCut, bool inv) return -1; } auto propagator = o2::base::Propagator::Instance(); // float version! - static float prevBz = -99999.; - if (prevBz != propagator->getNominalBz()) { - prevBz = propagator->getNominalBz(); - mRecoParam.setBfield(prevBz); + static bool firstCall = true; + if (firstCall) { + mRecoParam.init(propagator->getNominalBz()); + firstCall = false; } const auto* transformer = mController->getTRDTransformer(); auto algTrack = mController->getAlgTrack(); diff --git a/Detectors/Align/src/AlignmentTrack.cxx b/Detectors/Align/src/AlignmentTrack.cxx index 554d30e246e29..644ee07c64984 100644 --- a/Detectors/Align/src/AlignmentTrack.cxx +++ b/Detectors/Align/src/AlignmentTrack.cxx @@ -168,7 +168,7 @@ bool AlignmentTrack::calcResidDeriv(double* extendedParams, bool invert, int pFr // (like http://root.cern.ch/root/html/ROOT__Math__RichardsonDerivator.html) // const auto& algConf = AlignConfig::Instance(); - trackParam_t probD[kNRDClones]; // use this to vary supplied param for derivative calculation + trackPar_t probD[kNRDClones]; // use this to vary supplied param for derivative calculation double varDelta[kRichardsonN]; const int kInvElem[kNKinParBON] = {-1, 1, 1, -1, -1}; // @@ -511,7 +511,7 @@ bool AlignmentTrack::calcResiduals(const double* extendedParams, bool invert, in } //______________________________________________________ -bool AlignmentTrack::propagateParamToPoint(trackParam_t* tr, int nTr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, int signCorr) +bool AlignmentTrack::propagateParamToPoint(trackPar_t* tr, int nTr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, int signCorr) { // Propagate set of tracks to the point (only parameters, no error matrix) // VECTORIZE this @@ -521,7 +521,7 @@ bool AlignmentTrack::propagateParamToPoint(trackParam_t* tr, int nTr, const Alig if (!propagateParamToPoint(tr[itr], pnt, maxStep, maxSnp, mt, signCorr)) { if (algConf.verbose > 2) { LOG(error) << "Failed on clone " << itr << " propagation "; - tr[itr].print(); + tr[itr].printParam(); pnt->print(AlignmentPoint::kMeasurementBit | AlignmentPoint::kMaterialBit); } return false; @@ -531,21 +531,33 @@ bool AlignmentTrack::propagateParamToPoint(trackParam_t* tr, int nTr, const Alig } //______________________________________________________ -bool AlignmentTrack::propagateParamToPoint(trackParam_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, int signCorr) +bool AlignmentTrack::propagateParamToPoint(trackPar_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, int signCorr) { // propagate tracks to the point (only parameters, no error matrix) return propagate(tr, pnt, maxStep, maxSnp, mt, nullptr, signCorr); } //______________________________________________________ -bool AlignmentTrack::propagateToPoint(trackParam_t& tr, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr) +bool AlignmentTrack::propagateToPoint(trackParam_t& tr, trackPar_t* linRef, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr) { // propagate tracks to the point. If matCor is true, then material corrections will be applied. // if matPar pointer is provided, it will be filled by total x2x0 and signed xrho - return propagate(tr, pnt, maxStep, maxSnp, mt, tLT, signCorr); + return propagate(tr, linRef, pnt, maxStep, maxSnp, mt, tLT, signCorr); } -bool AlignmentTrack::propagate(trackParam_t& track, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr) +bool AlignmentTrack::propagate(trackParam_t& track, trackPar_t* linRef, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr) +{ + if (signCorr == 0) { // auto + // calculate the sign of the energy loss correction and ensure the upper leg of cosmics is calculated correctly. + double dx = pnt->getXTracking() - track.getX(); + int dir = dx > 0.f ? 1 : -1; + signCorr = pnt->isInvDir() ? dir : -dir; // propagation along the track direction should have signCorr=-1 + } + // do propagation in at least 2 step to reveal eventual effect of MS on the position + return PropagatorD::Instance()->propagateToAlphaX(track, linRef, pnt->getAlphaSens(), pnt->getXTracking(), pnt->getUseBzOnly(), maxSnp, maxStep, 2, mt, tLT, signCorr); +} + +bool AlignmentTrack::propagate(trackPar_t& track, const AlignmentPoint* pnt, double maxStep, double maxSnp, MatCorrType mt, track::TrackLTIntegral* tLT, int signCorr) { if (signCorr == 0) { // auto // calculate the sign of the energy loss correction and ensure the upper leg of cosmics is calculated correctly. @@ -603,7 +615,7 @@ bool AlignmentTrack::ApplyMS(trackParam_t& trPar, double tms,double pms) */ //______________________________________________________ -bool AlignmentTrack::applyMatCorr(trackParam_t& trPar, const double* corrPar, const AlignmentPoint* pnt) +bool AlignmentTrack::applyMatCorr(trackPar_t& trPar, const double* corrPar, const AlignmentPoint* pnt) { // Modify track param (e.g. trackParam_t) in the tracking frame // by delta accounting for material effects @@ -630,7 +642,7 @@ bool AlignmentTrack::applyMatCorr(trackParam_t& trPar, const double* corrPar, co } //______________________________________________________ -bool AlignmentTrack::applyMatCorr(trackParam_t& trPar, const double* corr) +bool AlignmentTrack::applyMatCorr(trackPar_t& trPar, const double* corr) { // Modify track param (e.g. trackParam_t) in the tracking frame // by delta accounting for material effects @@ -645,7 +657,7 @@ bool AlignmentTrack::applyMatCorr(trackParam_t& trPar, const double* corr) printf("%+.3e ", corr[i]); } printf("\n"); - trPar.print(); + trPar.printParam(); } return false; } @@ -656,7 +668,7 @@ bool AlignmentTrack::applyMatCorr(trackParam_t& trPar, const double* corr) } //______________________________________________________ -bool AlignmentTrack::applyMatCorr(trackParam_t* trSet, int ntr, const double* corrDiag, const AlignmentPoint* pnt) +bool AlignmentTrack::applyMatCorr(trackPar_t* trSet, int ntr, const double* corrDiag, const AlignmentPoint* pnt) { // Modify set of track params (e.g. trackParam_t) in the tracking frame // by delta accounting for material effects @@ -683,7 +695,7 @@ bool AlignmentTrack::applyMatCorr(trackParam_t* trSet, int ntr, const double* co if (!applyMatCorr(trSet[itr], corr)) { if (algConf.verbose > 2) { LOGP(error, "Failed on clone {} materials", itr); - trSet[itr].print(); + trSet[itr].printParam(); } return false; } @@ -732,7 +744,7 @@ double AlignmentTrack::richardsonExtrap(const double* val, int ord) } //______________________________________________ -void AlignmentTrack::richardsonDeriv(const trackParam_t* trSet, const double* delta, const AlignmentPoint* pnt, double& derY, double& derZ) +void AlignmentTrack::richardsonDeriv(const trackPar_t* trSet, const double* delta, const AlignmentPoint* pnt, double& derY, double& derZ) { // Calculate Richardson derivatives for diagonalized Y and Z from a set of kRichardsonN pairs // of tracks with same parameter of i-th pair varied by +-delta[i] @@ -882,7 +894,7 @@ bool AlignmentTrack::iniFit() // // propagate to reference point, which is the inner point of lower leg const AlignmentPoint* refP = getPoint(getInnerPointID()); - if (!propagateToPoint(trcU, refP, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, -1)) { // moving along the track: energy is lost + if (!propagateToPoint(trcU, nullptr, refP, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, -1)) { // moving along the track: energy is lost return false; } // @@ -1024,6 +1036,7 @@ bool AlignmentTrack::fitLeg(trackParam_t& trc, int pFrom, int pTo, bool& inv) } return false; } + trackPar_t linRef(trc), *linRefP = algConf.useLinRef ? &linRef : nullptr; trc.setCov(kIniErr); trc.setCov(16 * trc.getQ2Pt() * trc.getQ2Pt(), 4, 4); // lowest diagonal element (Q2Pt2) // @@ -1042,7 +1055,7 @@ bool AlignmentTrack::fitLeg(trackParam_t& trc, int pFrom, int pTo, bool& inv) int pntCnt = 0; for (int ip = pFrom; ip != pTo; ip += pinc) { // inward fit from outer point AlignmentPoint* pnt = getPoint(ip); - if (!propagateToPoint(trc, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { // against track direction : e.loss is compensated + if (!propagateToPoint(trc, linRefP, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { // against track direction : e.loss is compensated if (algConf.verbose > 2) { LOGF(warn, "Failed on propagateToPoint %d (%d : %d) %f", ip, pFrom, pTo, pnt->getXTracking()); trc.print(); @@ -1139,7 +1152,7 @@ bool AlignmentTrack::residKalman() trc.invert(); inv = !inv; } - if (!propagateToPoint(trc, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { + if (!propagateToPoint(trc, nullptr, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { return false; } if (!pnt->containsMeasurement()) { @@ -1178,7 +1191,7 @@ bool AlignmentTrack::residKalman() trc.invert(); inv = !inv; } - if (!propagateToPoint(trc, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { // we are going along track direction, e.loss is applied + if (!propagateToPoint(trc, nullptr, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), nullptr, signELoss)) { // we are going along track direction, e.loss is applied return false; } if (!pnt->containsMeasurement()) { @@ -1335,7 +1348,7 @@ bool AlignmentTrack::processMaterials(trackParam_t& trc, int pFrom, int pTo) // matTL.clearFast(); // printf("-> ProcMat %d (%d->%d)\n",ip,pFrom,pTo); - if (!propagateToPoint(trc, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), &matTL, signELoss)) { // with material corrections + if (!propagateToPoint(trc, nullptr, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType(algConf.matCorType), &matTL, signELoss)) { // with material corrections if (algConf.verbose > 2) { LOG(error) << "Failed to take track to point" << ip << " (dir: " << pFrom << "->" << pTo << ") with mat.corr."; trc.print(); @@ -1346,7 +1359,7 @@ bool AlignmentTrack::processMaterials(trackParam_t& trc, int pFrom, int pTo) // // is there enough material to consider the point as a scatterer? bool hasMaterial = matTL.getX2X0() > minX2X0; - if (!propagateToPoint(tr0, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType::USEMatCorrNONE, nullptr, signELoss)) { // no material corrections + if (!propagateToPoint(tr0, nullptr, pnt, algConf.maxStep, algConf.maxSnp, MatCorrType::USEMatCorrNONE, nullptr, signELoss)) { // no material corrections if (algConf.verbose > 2) { LOG(error) << "Failed to take track to point" << ip << " (dir: " << pFrom << "->" << pTo << ") with mat.corr."; tr0.print(); diff --git a/Detectors/Align/src/Controller.cxx b/Detectors/Align/src/Controller.cxx index a45314b2285c0..5f55d07893d33 100644 --- a/Detectors/Align/src/Controller.cxx +++ b/Detectors/Align/src/Controller.cxx @@ -44,7 +44,7 @@ #include #include #include -#include "GPUO2Interface.h" +#include "GPUO2ExternalUser.h" #include "DataFormatsTPC/WorkflowHelper.h" #include #include "CommonUtils/NameConf.h" @@ -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/CMakeLists.txt b/Detectors/Base/CMakeLists.txt index 0ba2905ab02ec..83a9193274e4f 100644 --- a/Detectors/Base/CMakeLists.txt +++ b/Detectors/Base/CMakeLists.txt @@ -8,6 +8,7 @@ # 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_compile_options(-O0 -g -fPIC) o2_add_library(DetectorsBase SOURCES src/Detector.cxx @@ -28,6 +29,8 @@ o2_add_library(DetectorsBase src/Stack.cxx src/VMCSeederService.cxx src/GlobalParams.cxx + src/O2Tessellated.cxx + src/TGeoGeometryUtils.cxx PUBLIC_LINK_LIBRARIES FairRoot::Base O2::CommonUtils O2::DetectorsCommonDataFormats @@ -45,6 +48,7 @@ o2_add_library(DetectorsBase O2::GPUDataTypes MC::VMC TBB::tbb + ROOT::Gdml ) o2_target_root_dictionary(DetectorsBase @@ -61,7 +65,9 @@ o2_target_root_dictionary(DetectorsBase include/DetectorsBase/Aligner.h include/DetectorsBase/Stack.h include/DetectorsBase/SimFieldUtils.h - include/DetectorsBase/GlobalParams.h) + include/DetectorsBase/GlobalParams.h + include/DetectorsBase/O2Tessellated.h + ) if(BUILD_SIMULATION) if (NOT APPLE) @@ -87,6 +93,7 @@ endif() install(FILES test/buildMatBudLUT.C test/extractLUTLayers.C + test/rescaleLUT.C DESTINATION share/macro/) o2_add_test_root_macro(test/buildMatBudLUT.C @@ -96,3 +103,7 @@ o2_add_test_root_macro(test/buildMatBudLUT.C o2_add_test_root_macro(test/extractLUTLayers.C PUBLIC_LINK_LIBRARIES O2::DetectorsBase LABELS detectorsbase) + +o2_add_test_root_macro(test/rescaleLUT.C + PUBLIC_LINK_LIBRARIES O2::DetectorsBase + LABELS detectorsbase) diff --git a/Detectors/Base/include/DetectorsBase/CTFCoderBase.h b/Detectors/Base/include/DetectorsBase/CTFCoderBase.h index bf4f37ecbeff5..e94123bb2b7ff 100644 --- a/Detectors/Base/include/DetectorsBase/CTFCoderBase.h +++ b/Detectors/Base/include/DetectorsBase/CTFCoderBase.h @@ -58,8 +58,8 @@ class CTFCoderBase Decoder }; CTFCoderBase() = delete; - CTFCoderBase(int n, DetID det, float memFactor = 1.f) : mCoders(n), mDet(det), mMemMarginFactor(memFactor > 1.f ? memFactor : 1.f) {} - CTFCoderBase(OpType op, int n, DetID det, float memFactor = 1.f) : mOpType(op), mCoders(n), mDet(det), mMemMarginFactor(memFactor > 1.f ? memFactor : 1.f) {} + CTFCoderBase(int n, DetID det, float memFactor = 1.f, const std::string& ctfdictOpt = "none") : mCoders(n), mDet(det), mMemMarginFactor(memFactor > 1.f ? memFactor : 1.f), mDictOpt{ctfdictOpt} {} + CTFCoderBase(OpType op, int n, DetID det, float memFactor = 1.f, const std::string& ctfdictOpt = "none") : mOpType(op), mCoders(n), mDet(det), mMemMarginFactor(memFactor > 1.f ? memFactor : 1.f), mDictOpt{ctfdictOpt} {} virtual ~CTFCoderBase() = default; virtual void createCoders(const std::vector& bufVec, o2::ctf::CTFCoderBase::OpType op) = 0; @@ -189,6 +189,7 @@ class CTFCoderBase std::vector loadDictionaryFromTree(TTree* tree); std::vector mCoders; // encoders/decoders DetID mDet; + std::string mDictOpt{}; std::string mDictBinding{"ctfdict"}; std::string mTrigOffsBinding{"trigoffset"}; CTFDictHeader mExtHeader; // external dictionary header @@ -313,6 +314,7 @@ void CTFCoderBase::init(o2::framework::InitContext& ic) if (ic.options().hasOption("irframe-shift")) { mIRFrameSelShift = (long)ic.options().get("irframe-shift"); } + bool ansVersionProvided = false; if (ic.options().hasOption("ans-version")) { if (ic.options().isSet("ans-version")) { const std::string ansVersionString = ic.options().get("ans-version"); @@ -322,18 +324,21 @@ void CTFCoderBase::init(o2::framework::InitContext& ic) if (mANSVersion == ANSVersionUnspecified) { throw std::invalid_argument(fmt::format("Invalid ANS Version {}", ansVersionString)); } + ansVersionProvided = true; } } } - auto dict = ic.options().get("ctf-dict"); - if (dict.empty() || dict == "ccdb") { // load from CCDB + if (mDictOpt.empty() || mDictOpt == "ccdb") { // load from CCDB mLoadDictFromCCDB = true; } else { - if (dict != "none") { // none means per-CTF dictionary will created on the fly - createCodersFromFile(dict, mOpType); - LOGP(info, "Loaded {} from {}", mExtHeader.asString(), dict); + if (mDictOpt != "none") { // none means per-CTF dictionary will created on the fly + createCodersFromFile(mDictOpt, mOpType); + LOGP(info, "Loaded {} from {}, ANS Version {}", mExtHeader.asString(), mDictOpt, std::string(mANSVersion)); } else { - LOGP(info, "Internal per-TF CTF Dict will be created"); + if (!ansVersionProvided) { + mANSVersion = ANSVersion1; + } + LOGP(info, "Internal per-TF CTF Dict will be created, ANS Version {}", std::string(mANSVersion)); } mLoadDictFromCCDB = false; // don't try to load from CCDB } @@ -368,7 +373,7 @@ bool CTFCoderBase::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, voi } createCoders(*dict, mOpType); mExtHeader = static_cast(CTF::get(dict->data())->getHeader()); - LOGP(info, "Loaded {} from CCDB", mExtHeader.asString()); + LOGP(info, "Loaded {} from CCDB, ANS Version {}", mExtHeader.asString(), std::string(mANSVersion)); } mLoadDictFromCCDB = false; // we read the dictionary at most once! } else if ((match = (matcher == o2::framework::ConcreteDataMatcher("CTP", "Trig_Offset", 0)))) { diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h index ca015fa457a1a..e63de51e0a6ca 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCyl.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCyl.h @@ -93,10 +93,12 @@ class MatLayerCyl : public o2::gpu::FlatObject GPUd() const MatCell& getCell(int iphiSlice, int iz) const { return mCells[getCellID(iphiSlice, iz)]; } #ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version - GPUd() MatCell& getCellPhiBin(int iphi, int iz) + MatCell& getCellPhiBin(int iphi, int iz) { return mCells[getCellIDPhiBin(iphi, iz)]; } + + void scale(float factor, bool _x2x0 = true, bool _rho = true); #endif // ---------------------- Z slice manipulation diff --git a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h index c74ce365d378f..cba6e5cebcfc8 100644 --- a/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h +++ b/Detectors/Base/include/DetectorsBase/MatLayerCylSet.h @@ -87,7 +87,7 @@ class MatLayerCylSet : public o2::gpu::FlatObject void flatten(); MatLayerCyl& getLayer(int i) { return get()->mLayers[i]; } - MatLayerCylSet* extractCopy(float rmin, float rmax, float tol = 1e-3) const; + MatLayerCylSet* extractCopy(float rmin, float rmax, float tol = 1e-3, const MatLayerCylSet* toAdd = nullptr) const; void finalizeStructures(); #endif // !GPUCA_ALIGPUCODE @@ -98,6 +98,10 @@ class MatLayerCylSet : public o2::gpu::FlatObject // get material budget traversed on the line between point0 and point1 return getMatBudget(point0.X(), point0.Y(), point0.Z(), point1.X(), point1.Y(), point1.Z()); } + + void scaleLayersByID(int lrFrom, int lrTo, float factor, bool _x2x0 = true, bool _rho = true); + void scaleLayersByR(float rFrom, float rTo, float factor, bool _x2x0 = true, bool _rho = true); + #endif // !GPUCA_ALIGPUCODE GPUd() MatBudget getMatBudget(float x0, float y0, float z0, float x1, float y1, float z1) const; diff --git a/Detectors/Base/include/DetectorsBase/MaterialManager.h b/Detectors/Base/include/DetectorsBase/MaterialManager.h index 4448998ee3d33..b0de75c2d6c84 100644 --- a/Detectors/Base/include/DetectorsBase/MaterialManager.h +++ b/Detectors/Base/include/DetectorsBase/MaterialManager.h @@ -218,7 +218,7 @@ class MaterialManager std::unordered_map mDensityMap; void initDensityMap(); - float getDensity(std::string const& modname); + float getDensity(std::string const& modname, std::string const& matname); // Hide details by providing these private methods so it cannot happen that special settings // are applied as default settings by accident using a boolean flag diff --git a/Detectors/Base/include/DetectorsBase/O2Tessellated.h b/Detectors/Base/include/DetectorsBase/O2Tessellated.h new file mode 100644 index 0000000000000..0a1cee8b3e01f --- /dev/null +++ b/Detectors/Base/include/DetectorsBase/O2Tessellated.h @@ -0,0 +1,142 @@ +// 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_BASE_O2TESSELLATED_ +#define ALICEO2_BASE_O2TESSELLATED_ + +#include "TGeoShape.h" +#include "TGeoBBox.h" +#include "TGeoVector3.h" +#include "TGeoTypedefs.h" +#include "TGeoTessellated.h" + +namespace o2 +{ +namespace base +{ + +class O2Tessellated : public TGeoBBox +{ + + public: + using Vertex_t = Tessellated::Vertex_t; + + private: + int fNfacets = 0; // Number of facets + int fNvert = 0; // Number of vertices + int fNseg = 0; // Number of segments + bool fDefined = false; //! Shape fully defined + bool fClosedBody = false; // The faces are making a closed body + + // for now separate vectors but might be better to group per face + std::vector fVertices; // List of vertices + std::vector fFacets; // List of facets + std::vector fOutwardNormals; // Vector of outward-facing normals (to be streamed !) + + std::multimap fVerticesMap; //! Temporary map used to deduplicate vertices + bool fIsClosed = false; //! to know if shape still needs closure/initialization + void* fBVH = nullptr; //! BVH acceleration structure for safety and navigation + + O2Tessellated(const O2Tessellated&) = delete; + O2Tessellated& operator=(const O2Tessellated&) = delete; + + // bvh helper functions + void BuildBVH(); + void CalculateNormals(); + + public: + // constructors + O2Tessellated() {} + O2Tessellated(const char* name, int nfacets = 0); + O2Tessellated(const char* name, const std::vector& vertices); + // from a TGeoTessellated + O2Tessellated(TGeoTessellated const&, bool check = false); + + // destructor + ~O2Tessellated() override {} + + void ComputeBBox() override; + void CloseShape(bool check = true, bool fixFlipped = true, bool verbose = true); + + bool AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2); + bool AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2, const Vertex_t& pt3); + bool AddFacet(int i1, int i2, int i3); + bool AddFacet(int i1, int i2, int i3, int i4); + int AddVertex(const Vertex_t& vert); + + bool FacetCheck(int ifacet) const; + Vertex_t FacetComputeNormal(int ifacet, bool& degenerated) const; + + int GetNfacets() const { return fFacets.size(); } + int GetNsegments() const { return fNseg; } + int GetNvertices() const { return fNvert; } + bool IsClosedBody() const { return fClosedBody; } + bool IsDefined() const { return fDefined; } + + const TGeoFacet& GetFacet(int i) const { return fFacets[i]; } + const Vertex_t& GetVertex(int i) const { return fVertices[i]; } + + int DistancetoPrimitive(int, int) override { return 99999; } + const TBuffer3D& GetBuffer3D(int reqSections, Bool_t localFrame) const override; + void GetMeshNumbers(int& nvert, int& nsegs, int& npols) const override; + int GetNmeshVertices() const override { return fNvert; } + void InspectShape() const override {} + TBuffer3D* MakeBuffer3D() const override; + void Print(Option_t* option = "") const override; + void SavePrimitive(std::ostream&, Option_t*) override {} + void SetPoints(double* points) const override; + void SetPoints(Float_t* points) const override; + void SetSegsAndPols(TBuffer3D& buff) const override; + void Sizeof3D() const override {} + + /// Resize and center the shape in a box of size maxsize + void ResizeCenter(double maxsize); + + /// Flip all facets + void FlipFacets() + { + for (auto facet : fFacets) + facet.Flip(); + } + + bool CheckClosure(bool fixFlipped = true, bool verbose = true); + + /// Reader from .obj format + static O2Tessellated* ImportFromObjFormat(const char* objfile, bool check = false, bool verbose = false); + + // navigation functions used by TGeoNavigator (attention: only the iact == 3 cases implemented for now) + Double_t DistFromOutside(const Double_t* point, const Double_t* dir, Int_t iact = 1, + Double_t step = TGeoShape::Big(), Double_t* safe = nullptr) const override; + Double_t DistFromInside(const Double_t* point, const Double_t* dir, Int_t iact = 1, Double_t step = TGeoShape::Big(), + Double_t* safe = nullptr) const override; + bool Contains(const Double_t* point) const override; + Double_t Safety(const Double_t* point, Bool_t in = kTRUE) const override; + void ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const override; + + // these are trivial implementations, just for debugging + Double_t DistFromInside_Loop(const Double_t* point, const Double_t* dir) const; + Double_t DistFromOutside_Loop(const Double_t* point, const Double_t* dir) const; + bool Contains_Loop(const Double_t* point) const; + + Double_t Capacity() const override; + + private: + // a safety kernel used in multiple implementations + template + Double_t SafetyKernel(const Double_t* point, bool in, int* closest_facet_id = nullptr) const; + + ClassDefOverride(O2Tessellated, 1) // tessellated shape class +}; + +} // namespace base +} // namespace o2 + +#endif diff --git a/Detectors/Base/include/DetectorsBase/Propagator.h b/Detectors/Base/include/DetectorsBase/Propagator.h index dbdef47e4edc0..75b9446aebade 100644 --- a/Detectors/Base/include/DetectorsBase/Propagator.h +++ b/Detectors/Base/include/DetectorsBase/Propagator.h @@ -76,6 +76,10 @@ class PropagatorImpl value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool PropagateToXBxByBz(TrackParCov_t& track, TrackPar_t& linRef, value_type x, + value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, + track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool PropagateToXBxByBz(TrackPar_t& track, value_type x, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; @@ -84,6 +88,10 @@ class PropagatorImpl value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool propagateToX(TrackParCov_t& track, TrackPar_t& linRef, value_type x, value_type bZ, + value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, + track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool propagateToX(TrackPar_t& track, value_type x, value_type bZ, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; @@ -92,13 +100,40 @@ class PropagatorImpl GPUd() bool propagateTo(track_T& track, value_type x, bool bzOnly = false, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const { - return bzOnly ? propagateToX(track, x, getNominalBz(), maxSnp, maxStep, matCorr, tofInfo, signCorr) : PropagateToXBxByBz(track, x, maxSnp, maxStep, matCorr, tofInfo, signCorr); + return bzOnly ? propagateToX(track, x, getBz(track.getXYZGlo()), maxSnp, maxStep, matCorr, tofInfo, signCorr) : PropagateToXBxByBz(track, x, maxSnp, maxStep, matCorr, tofInfo, signCorr); + } + + GPUd() bool propagateToX(TrackParCov_t& track, TrackPar_t* linRef, value_type x, value_type bZ, + value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, + track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const + { + return linRef ? propagateToX(track, *linRef, x, bZ, maxSnp, maxStep, matCorr, tofInfo, signCorr) : propagateToX(track, x, bZ, maxSnp, maxStep, matCorr, tofInfo, signCorr); + } + + GPUd() bool PropagateToXBxByBz(TrackParCov_t& track, TrackPar_t* linRef, value_type x, + value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, + track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const + { + return linRef ? PropagateToXBxByBz(track, *linRef, x, maxSnp, maxStep, matCorr, tofInfo, signCorr) : PropagateToXBxByBz(track, x, maxSnp, maxStep, matCorr, tofInfo, signCorr); + } + + GPUd() bool propagateTo(TrackParCov_t& track, TrackPar_t* linRef, value_type x, bool bzOnly = false, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, + MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const + { + return bzOnly ? propagateToX(track, linRef, x, getBz(track.getXYZGlo()), maxSnp, maxStep, matCorr, tofInfo, signCorr) : PropagateToXBxByBz(track, linRef, x, maxSnp, maxStep, matCorr, tofInfo, signCorr); } template GPUd() bool propagateToAlphaX(track_T& track, value_type alpha, value_type x, bool bzOnly = false, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, int minSteps = 1, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool propagateToAlphaX(TrackParCov_t& track, TrackPar_t* linRef, value_type alpha, value_type x, bool bzOnly = false, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, int minSteps = 1, + MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + + template + GPUd() bool propagateToR(track_T& track, value_type r, bool bzOnly = false, value_type maxSnp = MAX_SIN_PHI, value_type maxStep = MAX_STEP, + MatCorrType matCorr = MatCorrType::USEMatCorrLUT, track::TrackLTIntegral* tofInfo = nullptr, int signCorr = 0) const; + GPUd() bool propagateToDCA(const o2::dataformats::VertexBase& vtx, o2::track::TrackParametrizationWithError& track, value_type bZ, value_type maxStep = MAX_STEP, MatCorrType matCorr = MatCorrType::USEMatCorrLUT, o2::dataformats::DCA* dcaInfo = nullptr, track::TrackLTIntegral* tofInfo = nullptr, @@ -157,6 +192,10 @@ class PropagatorImpl GPUd() void getFieldXYZ(const math_utils::Point3D xyz, double* bxyz) const; + GPUd() float getBz(const math_utils::Point3D xyz) const; + + GPUd() double getBz(const math_utils::Point3D xyz) const; + private: #ifndef GPUCA_GPUCODE PropagatorImpl(bool uninitialized = false); @@ -165,6 +204,8 @@ class PropagatorImpl static constexpr value_type Epsilon = 0.00001; // precision of propagation to X template GPUd() void getFieldXYZImpl(const math_utils::Point3D xyz, T* bxyz) const; + template + GPUd() T getBzImpl(const math_utils::Point3D xyz) const; const o2::field::MagFieldFast* mFieldFast = nullptr; ///< External fast field map (barrel only for the moment) o2::field::MagneticField* mField = nullptr; ///< External nominal field map diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0Workflow.h b/Detectors/Base/include/DetectorsBase/TGeoGeometryUtils.h similarity index 51% rename from Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0Workflow.h rename to Detectors/Base/include/DetectorsBase/TGeoGeometryUtils.h index a4988b2c18fc7..5ec85f1c14702 100644 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0Workflow.h +++ b/Detectors/Base/include/DetectorsBase/TGeoGeometryUtils.h @@ -9,20 +9,30 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef O2_FIT_FT0WORKFLOW_H -#define O2_FIT_FT0WORKFLOW_H +/// \file TGeoGeometryUtils.h +/// \author Sandro Wenzel (CERN) +/// \brief Collection of utility functions for TGeo -/// @file FT0Workflow.h +#ifndef ALICEO2_BASE_TGEOGEOMETRYUTILS_H_ +#define ALICEO2_BASE_TGEOGEOMETRYUTILS_H_ -#include "Framework/WorkflowSpec.h" +class TGeoShape; +class TGeoTessellated; namespace o2 { -namespace ft0 +namespace base { -framework::WorkflowSpec getFT0Workflow(bool isExtendedMode, bool useProcess, - bool dumpProcessor, bool dumpReader, - bool disableRootOut, bool askSTFDist); -} // namespace ft0 + +/// A few utility functions to operate on TGeo geometries (transformations, printing, ...) +class TGeoGeometryUtils +{ + public: + ///< Transform any (primitive) TGeoShape to a tessellated representation + static TGeoTessellated* TGeoShapeToTGeoTessellated(TGeoShape const*); +}; + +} // namespace base } // namespace o2 + #endif diff --git a/Detectors/Base/src/DetectorsBaseLinkDef.h b/Detectors/Base/src/DetectorsBaseLinkDef.h index bd76e9bfbe2e4..8255c143ebb4a 100644 --- a/Detectors/Base/src/DetectorsBaseLinkDef.h +++ b/Detectors/Base/src/DetectorsBaseLinkDef.h @@ -42,4 +42,6 @@ #pragma link C++ class o2::data::Stack + ; +#pragma link C++ class o2::base::O2Tessellated - ; + #endif diff --git a/Detectors/Base/src/MatLayerCyl.cxx b/Detectors/Base/src/MatLayerCyl.cxx index 2346946ea6a8a..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,15 +313,30 @@ 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; } +//________________________________________________________________________________ +void MatLayerCyl::scale(float factor, bool _x2x0, bool _rho) +{ + LOGP(info, "Scaling layer {:.3f}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 // } @@ -256,6 +256,33 @@ void MatLayerCylSet::print(bool data) const float(getFlatBufferSize()) / 1024 / 1024); } +//________________________________________________________________________________ +void MatLayerCylSet::scaleLayersByID(int lrFrom, int lrTo, float factor, bool _x2x0, bool _rho) +{ + lrFrom = std::max(0, std::min(lrFrom, get()->mNLayers - 1)); + lrTo = std::max(0, std::min(lrTo, get()->mNLayers - 1)); + int dir = lrFrom >= lrTo ? -1 : 1; + lrTo += dir; + for (int i = lrFrom; i != lrTo; i += dir) { + get()->mLayers[i].scale(factor, _x2x0, _rho); + } +} + +//________________________________________________________________________________ +void MatLayerCylSet::scaleLayersByR(float rFrom, float rTo, float factor, bool _x2x0, bool _rho) +{ + if (rFrom > rTo) { + std::swap(rFrom, rTo); + } + Ray ray(std::max(getRMin(), rFrom), 0., 0., std::min(getRMax(), rTo), 0., 0.); + short lmin, lmax; + if (!getLayersRange(ray, lmin, lmax)) { + LOGP(warn, "No layers found for {} < r < {}", rFrom, rTo); + return; + } + scaleLayersByID(lmin, lmax, factor, _x2x0, _rho); +} + #endif //!GPUCA_ALIGPUCODE #ifndef GPUCA_GPUCODE @@ -481,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(); @@ -497,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++) { @@ -581,8 +608,12 @@ void MatLayerCylSet::fixPointers(char* oldPtr, char* newPtr, bool newPtrValid) #ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version -MatLayerCylSet* MatLayerCylSet::extractCopy(float rmin, float rmax, float tolerance) const +MatLayerCylSet* MatLayerCylSet::extractCopy(float rmin, float rmax, float tolerance, const MatLayerCylSet* addTo) const { + // extract layers in the covering rmin-rmax range. If addTo is provided, simply substitute its layers by those from this + if (addTo && addTo->getNLayers() != getNLayers()) { + LOGP(fatal, "addTo has {} layers, this has {}", addTo->getNLayers(), getNLayers()); + } Ray ray(std::max(getRMin(), rmin), 0., 0., std::min(getRMax(), rmax), 0., 0.); short lmin, lmax; if (!getLayersRange(ray, lmin, lmax)) { @@ -591,23 +622,37 @@ MatLayerCylSet* MatLayerCylSet::extractCopy(float rmin, float rmax, float tolera } LOGP(info, "Will extract layers {}:{} (out of {} layers) for {} < r < {}", lmin, lmax, getNLayers(), rmin, rmax); MatLayerCylSet* copy = new MatLayerCylSet(); - int lrCount = 0; - for (int il = lmin; il <= lmax; il++) { - const auto& lr = getLayer(il); + int lrCount = 0, lrCounOld = 0, lrCountTot = 0; + auto addLr = [copy, &lrCountTot](const MatLayerCyl& lr) { float drphi = lr.getDPhi() * (lr.getRMin() + lr.getRMax()) / 2. * 0.999; copy->addLayer(lr.getRMin(), lr.getRMax(), lr.getZMax(), lr.getDZ(), drphi); - auto& lrNew = copy->getLayer(lrCount); + auto& lrNew = copy->getLayer(lrCountTot++); for (int iz = 0; iz < lrNew.getNZBins(); iz++) { for (int ip = 0; ip < lrNew.getNPhiBins(); ip++) { lrNew.getCellPhiBin(ip, iz).set(lr.getCellPhiBin(ip, iz)); } } + }; + if (addTo) { + for (int il = 0; il < lmin; il++) { + addLr(addTo->getLayer(il)); + lrCounOld++; + } + } + for (int il = lmin; il <= lmax; il++) { + addLr(getLayer(il)); lrCount++; } - + if (addTo) { + for (int il = lmax + 1; il < getNLayers(); il++) { + addLr(addTo->getLayer(il)); + lrCounOld++; + } + } copy->finalizeStructures(); copy->optimizePhiSlices(tolerance); copy->flatten(); + LOGP(info, "Added layers {}:{} for {}second << " from material match"; + } + return iter->second; } - return o2::conf::SimMaterialParams::Instance().globalDensityFactor; + // density on module level + iter = mDensityMap.find(modname); + if (iter != mDensityMap.end()) { + if (debug) { + LOG(info) << "MatManager - " << modname << "/" << matname << " : applying density " << iter->second << " from module match"; + } + return iter->second; + } + // global factor + const auto global = o2::conf::SimMaterialParams::Instance().globalDensityFactor; + if (debug && global != 1.0) { + LOG(info) << "MatManager - " << modname << "/" << matname << " : applying global density " << iter->second; + } + return global; } void MaterialManager::Material(const char* modname, Int_t imat, const char* name, Float_t a, Float_t z, Float_t dens, Float_t radl, Float_t absl, Float_t* buf, Int_t nwbuf) { TString uniquename = modname; - auto densityFactor = getDensity(modname); + auto densityFactor = getDensity(modname, name); + uniquename.Append("_"); uniquename.Append(name); if (TVirtualMC::GetMC()) { @@ -173,7 +202,7 @@ void MaterialManager::Mixture(const char* modname, Int_t imat, const char* name, Int_t nlmat, Float_t* wmat) { TString uniquename = modname; - auto densityFactor = getDensity(modname); + auto densityFactor = getDensity(modname, name); uniquename.Append("_"); uniquename.Append(name); diff --git a/Detectors/Base/src/O2Tessellated.cxx b/Detectors/Base/src/O2Tessellated.cxx new file mode 100644 index 0000000000000..256a70e5a697a --- /dev/null +++ b/Detectors/Base/src/O2Tessellated.cxx @@ -0,0 +1,1509 @@ +// 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. + +// Sandro Wenzel 2026 + +// An implementation of TGeoTessellated augmented with efficient navigation functions. +// Asked for integration into ROOT here https://github.com/root-project/root/pull/21045 +// Will be deleted once we get this from ROOT. + +#include +#include + +#include "TGeoManager.h" +#include "TGeoMatrix.h" +#include "TGeoVolume.h" +#include "TVirtualGeoPainter.h" +#include "DetectorsBase/O2Tessellated.h" +#include "TBuffer3D.h" +#include "TBuffer3DTypes.h" +#include "TMath.h" +#include "TBuffer.h" + +#include +#include + +// THIS IS THIRD PARTY CODE (TO BE PUT IN ROOT) WHICH DOES NOT NEED TO ADHERE TO OUR LINTING +// NOLINTBEGIN + +// include the Third-party BVH headers +#include "bvh2_third_party.h" +// some kernels on top of BVH +#include "bvh2_extra_kernels.h" + +#include +#include + +using namespace o2::base; +ClassImp(O2Tessellated); + +using Vertex_t = Tessellated::Vertex_t; + +//////////////////////////////////////////////////////////////////////////////// +/// Compact consecutive equal vertices + +int TGeoFacet::CompactFacet(Vertex_t* vert, int nvertices) +{ + // Compact the common vertices and return new facet + if (nvertices < 2) + return nvertices; + int nvert = nvertices; + int i = 0; + while (i < nvert) { + if (vert[(i + 1) % nvert] == vert[i]) { + // shift last vertices left by one element + for (int j = i + 2; j < nvert; ++j) + vert[j - 1] = vert[j]; + nvert--; + } + i++; + } + return nvert; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Check if a connected neighbour facet has compatible normal + +bool TGeoFacet::IsNeighbour(const TGeoFacet& other, bool& flip) const +{ + + // Find a connecting segment + bool neighbour = false; + int line1[2], line2[2]; + int npoints = 0; + for (int i = 0; i < fNvert; ++i) { + auto ivert = fIvert[i]; + // Check if the other facet has the same vertex + for (int j = 0; j < other.GetNvert(); ++j) { + if (ivert == other[j]) { + line1[npoints] = i; + line2[npoints] = j; + if (++npoints == 2) { + neighbour = true; + bool order1 = line1[1] == line1[0] + 1; + bool order2 = line2[1] == (line2[0] + 1) % other.GetNvert(); + flip = (order1 == order2); + return neighbour; + } + } + } + } + return neighbour; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor. In case nfacets is zero, it is user's responsibility to +/// call CloseShape once all faces are defined. + +O2Tessellated::O2Tessellated(const char* name, int nfacets) : TGeoBBox(name, 0, 0, 0) +{ + fNfacets = nfacets; + if (nfacets) + fFacets.reserve(nfacets); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor providing directly the array of vertices. Facets have to be added +/// providing vertex indices rather than coordinates. + +O2Tessellated::O2Tessellated(const char* name, const std::vector& vertices) : TGeoBBox(name, 0, 0, 0) +{ + fVertices = vertices; + fNvert = fVertices.size(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Construct from TGeoTessellated + +O2Tessellated::O2Tessellated(TGeoTessellated const& tsl, bool check) : TGeoBBox(tsl.GetName(), 0, 0, 0) +{ + fNfacets = tsl.GetNfacets(); + fNvert = tsl.GetNvertices(); + fNseg = tsl.GetNsegments(); + + // copy facet and vertex done + fVertices.reserve(fNvert); + fFacets.reserve(fNfacets); + for (int i = 0; i < fNfacets; ++i) { + fFacets.push_back(tsl.GetFacet(i)); + } + for (int i = 0; i < fNvert; ++i) { + fVertices.push_back(tsl.GetVertex(i)); + } + // finish remaining structures + CloseShape(check); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Add a vertex checking for duplicates, returning the vertex index + +int O2Tessellated::AddVertex(Vertex_t const& vert) +{ + constexpr double tolerance = 1.e-10; + auto vertexHash = [&](Vertex_t const& vertex) { + // Compute hash for the vertex + long hash = 0; + // helper function to generate hash from integer numbers + auto hash_combine = [](long seed, const long value) { + return seed ^ (std::hash{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2)); + }; + for (int i = 0; i < 3; i++) { + // use tolerance to generate int with the desired precision from a real number for hashing + hash = hash_combine(hash, std::roundl(vertex[i] / tolerance)); + } + return hash; + }; + + auto hash = vertexHash(vert); + bool isAdded = false; + int ivert = -1; + // Get the compatible vertices + auto range = fVerticesMap.equal_range(hash); + for (auto it = range.first; it != range.second; ++it) { + ivert = it->second; + if (fVertices[ivert] == vert) { + isAdded = true; + break; + } + } + if (!isAdded) { + ivert = fVertices.size(); + fVertices.push_back(vert); + fVerticesMap.insert(std::make_pair(hash, ivert)); + } + return ivert; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Adding a triangular facet from vertex positions in absolute coordinates + +bool O2Tessellated::AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2) +{ + if (fDefined) { + Error("AddFacet", "Shape %s already fully defined. Not adding", GetName()); + return false; + } + + Vertex_t vert[3]; + vert[0] = pt0; + vert[1] = pt1; + vert[2] = pt2; + int nvert = TGeoFacet::CompactFacet(vert, 3); + if (nvert < 3) { + Error("AddFacet", "Triangular facet at index %d degenerated. Not adding.", GetNfacets()); + return false; + } + int ind[3]; + for (auto i = 0; i < 3; ++i) + ind[i] = AddVertex(vert[i]); + fNseg += 3; + fFacets.emplace_back(ind[0], ind[1], ind[2]); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Adding a triangular facet from indices of vertices + +bool O2Tessellated::AddFacet(int i0, int i1, int i2) +{ + if (fDefined) { + Error("AddFacet", "Shape %s already fully defined. Not adding", GetName()); + return false; + } + if (fVertices.empty()) { + Error("AddFacet", "Shape %s Cannot add facets by indices without vertices. Not adding", GetName()); + return false; + } + + fNseg += 3; + fFacets.emplace_back(i0, i1, i2); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Adding a quadrilateral facet from vertex positions in absolute coordinates + +bool O2Tessellated::AddFacet(const Vertex_t& pt0, const Vertex_t& pt1, const Vertex_t& pt2, const Vertex_t& pt3) +{ + if (fDefined) { + Error("AddFacet", "Shape %s already fully defined. Not adding", GetName()); + return false; + } + Vertex_t vert[4]; + vert[0] = pt0; + vert[1] = pt1; + vert[2] = pt2; + vert[3] = pt3; + int nvert = TGeoFacet::CompactFacet(vert, 4); + if (nvert < 3) { + Error("AddFacet", "Quadrilateral facet at index %d degenerated. Not adding.", GetNfacets()); + return false; + } + + int ind[4]; + for (auto i = 0; i < nvert; ++i) + ind[i] = AddVertex(vert[i]); + fNseg += nvert; + if (nvert == 3) + fFacets.emplace_back(ind[0], ind[1], ind[2]); + else + fFacets.emplace_back(ind[0], ind[1], ind[2], ind[3]); + + if (fNfacets > 0 && GetNfacets() == fNfacets) + CloseShape(false); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Adding a quadrilateral facet from indices of vertices + +bool O2Tessellated::AddFacet(int i0, int i1, int i2, int i3) +{ + if (fDefined) { + Error("AddFacet", "Shape %s already fully defined. Not adding", GetName()); + return false; + } + if (fVertices.empty()) { + Error("AddFacet", "Shape %s Cannot add facets by indices without vertices. Not adding", GetName()); + return false; + } + + fNseg += 4; + fFacets.emplace_back(i0, i1, i2, i3); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Compute normal for a given facet + +Vertex_t O2Tessellated::FacetComputeNormal(int ifacet, bool& degenerated) const +{ + // Compute normal using non-zero segments + constexpr double kTolerance = 1.e-20; + auto const& facet = fFacets[ifacet]; + int nvert = facet.GetNvert(); + degenerated = true; + Vertex_t normal; + for (int i = 0; i < nvert - 1; ++i) { + Vertex_t e1 = fVertices[facet[i + 1]] - fVertices[facet[i]]; + if (e1.Mag2() < kTolerance) + continue; + for (int j = i + 1; j < nvert; ++j) { + Vertex_t e2 = fVertices[facet[(j + 1) % nvert]] - fVertices[facet[j]]; + if (e2.Mag2() < kTolerance) + continue; + normal = Vertex_t::Cross(e1, e2); + // e1 and e2 may be colinear + if (normal.Mag2() < kTolerance) + continue; + normal.Normalize(); + degenerated = false; + break; + } + if (!degenerated) + break; + } + return normal; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Check validity of facet + +bool O2Tessellated::FacetCheck(int ifacet) const +{ + constexpr double kTolerance = 1.e-10; + auto const& facet = fFacets[ifacet]; + int nvert = facet.GetNvert(); + bool degenerated = true; + FacetComputeNormal(ifacet, degenerated); + if (degenerated) { + std::cout << "Facet: " << ifacet << " is degenerated\n"; + return false; + } + + // Compute surface area + double surfaceArea = 0.; + for (int i = 1; i < nvert - 1; ++i) { + Vertex_t e1 = fVertices[facet[i]] - fVertices[facet[0]]; + Vertex_t e2 = fVertices[facet[i + 1]] - fVertices[facet[0]]; + surfaceArea += 0.5 * Vertex_t::Cross(e1, e2).Mag(); + } + if (surfaceArea < kTolerance) { + std::cout << "Facet: " << ifacet << " has zero surface area\n"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Close the shape: calculate bounding box and compact vertices + +void O2Tessellated::CloseShape(bool check, bool fixFlipped, bool verbose) +{ + if (fIsClosed && fBVH) { + return; + } + // Compute bounding box + fDefined = true; + fNvert = fVertices.size(); + fNfacets = fFacets.size(); + ComputeBBox(); + + BuildBVH(); + if (fOutwardNormals.size() == 0) { + CalculateNormals(); + } else { + // short check if the normal container is of correct size + if (fOutwardNormals.size() != fFacets.size()) { + std::cerr << "Inconsistency in normal container"; + } + } + fIsClosed = true; + + // Cleanup the vertex map + std::multimap().swap(fVerticesMap); + + if (fVertices.size() > 0) { + if (!check) + return; + + // Check facets + for (auto i = 0; i < fNfacets; ++i) + FacetCheck(i); + + fClosedBody = CheckClosure(fixFlipped, verbose); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Check closure of the solid and check/fix flipped normals + +bool O2Tessellated::CheckClosure(bool fixFlipped, bool verbose) +{ + int* nn = new int[fNfacets]; + bool* flipped = new bool[fNfacets]; + bool hasorphans = false; + bool hasflipped = false; + for (int i = 0; i < fNfacets; ++i) { + nn[i] = 0; + flipped[i] = false; + } + + for (int icrt = 0; icrt < fNfacets; ++icrt) { + // all neighbours checked? + if (nn[icrt] >= fFacets[icrt].GetNvert()) + continue; + for (int i = icrt + 1; i < fNfacets; ++i) { + bool isneighbour = fFacets[icrt].IsNeighbour(fFacets[i], flipped[i]); + if (isneighbour) { + if (flipped[icrt]) + flipped[i] = !flipped[i]; + if (flipped[i]) + hasflipped = true; + nn[icrt]++; + nn[i]++; + if (nn[icrt] == fFacets[icrt].GetNvert()) + break; + } + } + if (nn[icrt] < fFacets[icrt].GetNvert()) + hasorphans = true; + } + + if (hasorphans && verbose) { + Error("Check", "Tessellated solid %s has following not fully connected facets:", GetName()); + for (int icrt = 0; icrt < fNfacets; ++icrt) { + if (nn[icrt] < fFacets[icrt].GetNvert()) + std::cout << icrt << " (" << fFacets[icrt].GetNvert() << " edges, " << nn[icrt] << " neighbours)\n"; + } + } + fClosedBody = !hasorphans; + int nfixed = 0; + if (hasflipped) { + if (verbose) + Warning("Check", "Tessellated solid %s has following facets with flipped normals:", GetName()); + for (int icrt = 0; icrt < fNfacets; ++icrt) { + if (flipped[icrt]) { + if (verbose) + std::cout << icrt << "\n"; + if (fixFlipped) { + fFacets[icrt].Flip(); + nfixed++; + } + } + } + if (nfixed && verbose) + Info("Check", "Automatically flipped %d facets to match first defined facet", nfixed); + } + delete[] nn; + delete[] flipped; + + return !hasorphans; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Compute bounding box + +void O2Tessellated::ComputeBBox() +{ + const double kBig = TGeoShape::Big(); + double vmin[3] = {kBig, kBig, kBig}; + double vmax[3] = {-kBig, -kBig, -kBig}; + for (const auto& facet : fFacets) { + for (int i = 0; i < facet.GetNvert(); ++i) { + for (int j = 0; j < 3; ++j) { + vmin[j] = TMath::Min(vmin[j], fVertices[facet[i]].operator[](j)); + vmax[j] = TMath::Max(vmax[j], fVertices[facet[i]].operator[](j)); + } + } + } + fDX = 0.5 * (vmax[0] - vmin[0]); + fDY = 0.5 * (vmax[1] - vmin[1]); + fDZ = 0.5 * (vmax[2] - vmin[2]); + for (int i = 0; i < 3; ++i) + fOrigin[i] = 0.5 * (vmax[i] + vmin[i]); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns numbers of vertices, segments and polygons composing the shape mesh. + +void O2Tessellated::GetMeshNumbers(int& nvert, int& nsegs, int& npols) const +{ + nvert = fNvert; + nsegs = fNseg; + npols = GetNfacets(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Creates a TBuffer3D describing *this* shape. +/// Coordinates are in local reference frame. + +TBuffer3D* O2Tessellated::MakeBuffer3D() const +{ + const int nvert = fNvert; + const int nsegs = fNseg; + const int npols = GetNfacets(); + auto buff = new TBuffer3D(TBuffer3DTypes::kGeneric, nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols); + if (buff) { + SetPoints(buff->fPnts); + SetSegsAndPols(*buff); + } + return buff; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Prints basic info + +void O2Tessellated::Print(Option_t*) const +{ + std::cout << "=== Tessellated shape " << GetName() << " having " << GetNvertices() << " vertices and " + << GetNfacets() << " facets\n"; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Fills TBuffer3D structure for segments and polygons. + +void O2Tessellated::SetSegsAndPols(TBuffer3D& buff) const +{ + const int c = GetBasicColor(); + int* segs = buff.fSegs; + int* pols = buff.fPols; + + int indseg = 0; // segment internal data index + int indpol = 0; // polygon internal data index + int sind = 0; // segment index + for (const auto& facet : fFacets) { + auto nvert = facet.GetNvert(); + pols[indpol++] = c; + pols[indpol++] = nvert; + for (auto j = 0; j < nvert; ++j) { + int k = (j + 1) % nvert; + // segment made by next consecutive points + segs[indseg++] = c; + segs[indseg++] = facet[j]; + segs[indseg++] = facet[k]; + // add segment to current polygon and increment segment index + pols[indpol + nvert - j - 1] = sind++; + } + indpol += nvert; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Fill tessellated points to an array. + +void O2Tessellated::SetPoints(double* points) const +{ + int ind = 0; + for (const auto& vertex : fVertices) { + vertex.CopyTo(&points[ind]); + ind += 3; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Fill tessellated points in float. + +void O2Tessellated::SetPoints(Float_t* points) const +{ + int ind = 0; + for (const auto& vertex : fVertices) { + points[ind++] = vertex.x(); + points[ind++] = vertex.y(); + points[ind++] = vertex.z(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Resize the shape by scaling vertices within maxsize and center to origin + +void O2Tessellated::ResizeCenter(double maxsize) +{ + using Vector3_t = Vertex_t; + + if (!fDefined) { + Error("ResizeCenter", "Not all faces are defined"); + return; + } + Vector3_t origin(fOrigin[0], fOrigin[1], fOrigin[2]); + double maxedge = TMath::Max(TMath::Max(fDX, fDY), fDZ); + double scale = maxsize / maxedge; + for (size_t i = 0; i < fVertices.size(); ++i) { + fVertices[i] = scale * (fVertices[i] - origin); + } + fOrigin[0] = fOrigin[1] = fOrigin[2] = 0; + fDX *= scale; + fDY *= scale; + fDZ *= scale; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Fills a static 3D buffer and returns a reference. + +const TBuffer3D& O2Tessellated::GetBuffer3D(int reqSections, Bool_t localFrame) const +{ + static TBuffer3D buffer(TBuffer3DTypes::kGeneric); + + FillBuffer3D(buffer, reqSections, localFrame); + + const int nvert = fNvert; + const int nsegs = fNseg; + const int npols = GetNfacets(); + + if (reqSections & TBuffer3D::kRawSizes) { + if (buffer.SetRawSizes(nvert, 3 * nvert, nsegs, 3 * nsegs, npols, 6 * npols)) { + buffer.SetSectionsValid(TBuffer3D::kRawSizes); + } + } + if ((reqSections & TBuffer3D::kRaw) && buffer.SectionsValid(TBuffer3D::kRawSizes)) { + SetPoints(buffer.fPnts); + if (!buffer.fLocalFrame) { + TransformPoints(buffer.fPnts, buffer.NbPnts()); + } + + SetSegsAndPols(buffer); + buffer.SetSectionsValid(TBuffer3D::kRaw); + } + + return buffer; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Reads a single tessellated solid from an .obj file. + +O2Tessellated* O2Tessellated::ImportFromObjFormat(const char* objfile, bool check, bool verbose) +{ + using std::vector, std::string, std::ifstream, std::stringstream, std::endl; + + vector vertices; + vector sfacets; + + struct FacetInd_t { + int i0 = -1; + int i1 = -1; + int i2 = -1; + int i3 = -1; + int nvert = 0; + FacetInd_t(int a, int b, int c) + { + i0 = a; + i1 = b; + i2 = c; + nvert = 3; + }; + FacetInd_t(int a, int b, int c, int d) + { + i0 = a; + i1 = b; + i2 = c; + i3 = d; + nvert = 4; + }; + }; + + vector facets; + // List of geometric vertices, with (x, y, z [,w]) coordinates, w is optional and defaults to 1.0. + // struct vtx_t { double x = 0; double y = 0; double z = 0; double w = 1; }; + + // Texture coordinates in u, [,v ,w]) coordinates, these will vary between 0 and 1. v, w are optional and default to + // 0. + // struct tex_t { double u; double v; double w; }; + + // List of vertex normals in (x,y,z) form; normals might not be unit vectors. + // struct vn_t { double x; double y; double z; }; + + // Parameter space vertices in ( u [,v] [,w] ) form; free form geometry statement + // struct vp_t { double u; double v; double w; }; + + // Faces are defined using lists of vertex, texture and normal indices which start at 1. + // Polygons such as quadrilaterals can be defined by using more than three vertex/texture/normal indices. + // f v1//vn1 v2//vn2 v3//vn3 ... + + // Records starting with the letter "l" specify the order of the vertices which build a polyline. + // l v1 v2 v3 v4 v5 v6 ... + + string line; + int ind[4] = {0}; + ifstream file(objfile); + if (!file.is_open()) { + ::Error("O2Tessellated::ImportFromObjFormat", "Unable to open %s", objfile); + return nullptr; + } + + while (getline(file, line)) { + stringstream ss(line); + string tag; + + // We ignore everything which is not a vertex or a face + if (line.rfind('v', 0) == 0 && line.rfind("vt", 0) != 0 && line.rfind("vn", 0) != 0 && line.rfind("vn", 0) != 0) { + // Decode the vertex + double pos[4] = {0, 0, 0, 1}; + ss >> tag >> pos[0] >> pos[1] >> pos[2] >> pos[3]; + vertices.emplace_back(pos[0] * pos[3], pos[1] * pos[3], pos[2] * pos[3]); + } + + else if (line.rfind('f', 0) == 0) { + // Decode the face + ss >> tag; + string word; + sfacets.clear(); + while (ss >> word) + sfacets.push_back(word); + if (sfacets.size() > 4 || sfacets.size() < 3) { + ::Error("O2Tessellated::ImportFromObjFormat", "Detected face having unsupported %zu vertices", + sfacets.size()); + return nullptr; + } + int nvert = 0; + for (auto& sword : sfacets) { + stringstream ssword(sword); + string token; + getline(ssword, token, '/'); // just need the vertex index, which is the first token + // Convert string token to integer + + ind[nvert++] = stoi(token) - 1; + if (ind[nvert - 1] < 0) { + ::Error("O2Tessellated::ImportFromObjFormat", "Unsupported relative vertex index definition in %s", + objfile); + return nullptr; + } + } + if (nvert == 3) + facets.emplace_back(ind[0], ind[1], ind[2]); + else + facets.emplace_back(ind[0], ind[1], ind[2], ind[3]); + } + } + + int nvertices = (int)vertices.size(); + int nfacets = (int)facets.size(); + if (nfacets < 3) { + ::Error("O2Tessellated::ImportFromObjFormat", "Not enough faces detected in %s", objfile); + return nullptr; + } + + string sobjfile(objfile); + if (verbose) + std::cout << "Read " << nvertices << " vertices and " << nfacets << " facets from " << sobjfile << endl; + + auto tsl = new O2Tessellated(sobjfile.erase(sobjfile.find_last_of('.')).c_str(), vertices); + + for (int i = 0; i < nfacets; ++i) { + auto facet = facets[i]; + if (facet.nvert == 3) + tsl->AddFacet(facet.i0, facet.i1, facet.i2); + else + tsl->AddFacet(facet.i0, facet.i1, facet.i2, facet.i3); + } + tsl->CloseShape(check, true, verbose); + tsl->Print(); + return tsl; +} + +// implementation of some geometry helper functions in anonymous namespace +namespace +{ + +using Vertex_t = Tessellated::Vertex_t; +// The classic Moeller-Trumbore ray triangle-intersection kernel: +// - Compute triangle edges e1, e2 +// - Compute determinant det +// - Reject parallel rays +// - Compute barycentric coordinates u, v +// - Compute ray parameter t +double rayTriangle(const Vertex_t& orig, const Vertex_t& dir, const Vertex_t& v0, const Vertex_t& v1, + const Vertex_t& v2, double rayEPS = 1e-8) +{ + constexpr double EPS = 1e-8; + const double INF = std::numeric_limits::infinity(); + Vertex_t e1{v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]}; + Vertex_t e2{v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]}; + auto p = Vertex_t::Cross(dir, e2); + auto det = e1.Dot(p); + if (std::abs(det) <= EPS) { + return INF; + } + + Vertex_t tvec{orig[0] - v0[0], orig[1] - v0[1], orig[2] - v0[2]}; + auto invDet = 1.0 / det; + auto u = tvec.Dot(p) * invDet; + if (u < 0.0 || u > 1.0) { + return INF; + } + auto q = Vertex_t::Cross(tvec, e1); + auto v = dir.Dot(q) * invDet; + if (v < 0.0 || u + v > 1.0) { + return INF; + } + auto t = e2.Dot(q) * invDet; + return (t > rayEPS) ? t : INF; +} + +template +struct Vec3f { + T x, y, z; +}; + +template +inline Vec3f operator-(const Vec3f& a, const Vec3f& b) +{ + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +template +inline Vec3f cross(const Vec3f& a, const Vec3f& b) +{ + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; +} + +template +inline T dot(const Vec3f& a, const Vec3f& b) +{ + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +// Kernel to get closest/shortest distance between a point and a triangl (a,b,c). +// Performed by default in float since Safety can be approximate. +// Project point onto triangle plane +// If projection lies inside → distance to plane +// Otherwise compute min distance to the three edges +// Return squared distance +template +T pointTriangleDistSq(const Vec3f& p, const Vec3f& a, const Vec3f& b, const Vec3f& c) +{ + // Edges + Vec3f ab = b - a; + Vec3f ac = c - a; + Vec3f ap = p - a; + + auto d1 = dot(ab, ap); + auto d2 = dot(ac, ap); + if (d1 <= T(0.0) && d2 <= T(0.0)) { + return dot(ap, ap); // barycentric (1,0,0) + } + + Vec3f bp = p - b; + auto d3 = dot(ab, bp); + auto d4 = dot(ac, bp); + if (d3 >= T(0.0) && d4 <= d3) { + return dot(bp, bp); // (0,1,0) + } + + T vc = d1 * d4 - d3 * d2; + if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) { + T v = d1 / (d1 - d3); + Vec3f proj = {a.x + v * ab.x, a.y + v * ab.y, a.z + v * ab.z}; + Vec3f d = p - proj; + return dot(d, d); // edge AB + } + + Vec3f cp = p - c; + T d5 = dot(ab, cp); + T d6 = dot(ac, cp); + if (d6 >= T(0.0f) && d5 <= d6) { + return dot(cp, cp); // (0,0,1) + } + + T vb = d5 * d2 - d1 * d6; + if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) { + T w = d2 / (d2 - d6); + Vec3f proj = {a.x + w * ac.x, a.y + w * ac.y, a.z + w * ac.z}; + Vec3f d = p - proj; + return dot(d, d); // edge AC + } + + T va = d3 * d6 - d5 * d4; + if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) { + T w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + Vec3f proj = {b.x + w * (c.x - b.x), b.y + w * (c.y - b.y), b.z + w * (c.z - b.z)}; + Vec3f d = p - proj; + return dot(d, d); // edge BC + } + + // Inside face region + T denom = T(1.0f) / (va + vb + vc); + T v = vb * denom; + T w = vc * denom; + + Vec3f proj = {a.x + ab.x * v + ac.x * w, a.y + ab.y * v + ac.y * w, a.z + ab.z * v + ac.z * w}; + + Vec3f d = p - proj; + return dot(d, d); +} + +template +inline Vec3f normalize(const Vec3f& v) +{ + T len2 = dot(v, v); + if (len2 == T(0.0f)) { + std::cerr << "Degnerate triangle. Cannot determine normal"; + return {0, 0, 0}; + } + T invLen = T(1.0f) / std::sqrt(len2); + return {v.x * invLen, v.y * invLen, v.z * invLen}; +} + +template +inline Vec3f triangleNormal(const Vec3f& a, const Vec3f& b, const Vec3f& c) +{ + const Vec3f e1 = b - a; + const Vec3f e2 = c - a; + return normalize(cross(e1, e2)); +} + +} // end anonymous namespace + +//////////////////////////////////////////////////////////////////////////////// +/// DistFromOutside + +Double_t O2Tessellated::DistFromOutside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t stepmax, + Double_t* /*safe*/) const +{ + // use the BVH intersector in combination with leaf ray-triangle testing + double local_step = Big(); // we need this otherwise the lambda get's confused + + using Scalar = float; + using Vec3 = bvh::v2::Vec; + using Node = bvh::v2::Node; + using Bvh = bvh::v2::Bvh; + using Ray = bvh::v2::Ray; + + // let's fetch the bvh + auto mybvh = (Bvh*)fBVH; + if (!mybvh) { + assert(false); + return -1.; + } + + auto truncate_roundup = [](double orig) { + float epsilon = std::numeric_limits::epsilon() * std::fabs(orig); + // Add the bias to x before assigning it to y + return static_cast(orig + epsilon); + }; + + // let's do very quick checks against the top node + const auto topnode_bbox = mybvh->get_root().get_bbox(); + if ((-point[0] + topnode_bbox.min[0]) > stepmax) { + return Big(); + } + if ((-point[1] + topnode_bbox.min[1]) > stepmax) { + return Big(); + } + if ((-point[2] + topnode_bbox.min[2]) > stepmax) { + return Big(); + } + if ((point[0] - topnode_bbox.max[0]) > stepmax) { + return Big(); + } + if ((point[1] - topnode_bbox.max[1]) > stepmax) { + return Big(); + } + if ((point[2] - topnode_bbox.max[2]) > stepmax) { + return Big(); + } + + // the ray used for bvh interaction + Ray ray(Vec3(point[0], point[1], point[2]), // origin + Vec3(dir[0], dir[1], dir[2]), // direction + 0.0f, // minimum distance (could give stepmax ?) + truncate_roundup(local_step)); + + static constexpr bool use_robust_traversal = true; + + Vertex_t dir_v{dir[0], dir[1], dir[2]}; + // Traverse the BVH and apply concrete object intersection in BVH leafs + bvh::v2::GrowingStack stack; + mybvh->intersect(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) { + for (size_t prim_id = begin; prim_id < end; ++prim_id) { + auto objectid = mybvh->prim_ids[prim_id]; + const auto& facet = fFacets[objectid]; + const auto& n = fOutwardNormals[objectid]; + + // quick normal test. Coming from outside, the dot product must be negative + if (n.Dot(dir_v) > 0.) { + continue; + } + + auto thisdist = rayTriangle(Vertex_t(point[0], point[1], point[2]), dir_v, + fVertices[facet[0]], fVertices[facet[1]], fVertices[facet[2]], 0.); + + if (thisdist < local_step) { + local_step = thisdist; + } + } + return false; // go on after this + }); + + return local_step; +} + +//////////////////////////////////////////////////////////////////////////////// +/// DistFromOutside + +Double_t O2Tessellated::DistFromInside(const Double_t* point, const Double_t* dir, Int_t /*iact*/, Double_t /*stepmax*/, + Double_t* /*safe*/) const +{ + // use the BVH intersector in combination with leaf ray-triangle testing + double local_step = Big(); // we need this otherwise the lambda get's confused + + using Scalar = float; + using Vec3 = bvh::v2::Vec; + using Node = bvh::v2::Node; + using Bvh = bvh::v2::Bvh; + using Ray = bvh::v2::Ray; + + // let's fetch the bvh + auto mybvh = (Bvh*)fBVH; + if (!mybvh) { + assert(false); + return -1.; + } + + auto truncate_roundup = [](double orig) { + float epsilon = std::numeric_limits::epsilon() * std::fabs(orig); + // Add the bias to x before assigning it to y + return static_cast(orig + epsilon); + }; + + // the ray used for bvh interaction + Ray ray(Vec3(point[0], point[1], point[2]), // origin + Vec3(dir[0], dir[1], dir[2]), // direction + 0., // minimum distance (could give stepmax ?) + truncate_roundup(local_step)); + + static constexpr bool use_robust_traversal = true; + + Vertex_t dir_v{dir[0], dir[1], dir[2]}; + // Traverse the BVH and apply concrete object intersection in BVH leafs + bvh::v2::GrowingStack stack; + mybvh->intersect(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) { + for (size_t prim_id = begin; prim_id < end; ++prim_id) { + auto objectid = mybvh->prim_ids[prim_id]; + auto facet = fFacets[objectid]; + const auto& n = fOutwardNormals[objectid]; + + // Only exiting surfaces are relevant (from inside--> dot product must be positive) + if (n.Dot(dir_v) <= 0.) { + continue; + } + + const auto& v0 = fVertices[facet[0]]; + const auto& v1 = fVertices[facet[1]]; + const auto& v2 = fVertices[facet[2]]; + + const double t = + rayTriangle(Vertex_t{point[0], point[1], point[2]}, dir_v, v0, v1, v2, 0.); + if (t < local_step) { + local_step = t; + } + } + return false; // go on after this + }); + + return local_step; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Capacity + +Double_t O2Tessellated::Capacity() const +{ + // For explanation of the following algorithm see: + // https://en.wikipedia.org/wiki/Polyhedron#Volume + // http://wwwf.imperial.ac.uk/~rn/centroid.pdf + + double vol = 0.0; + for (size_t i = 0; i < fFacets.size(); ++i) { + auto& facet = fFacets[i]; + auto a = fVertices[facet[0]]; + auto b = fVertices[facet[1]]; + auto c = fVertices[facet[2]]; + vol += + a[0] * (b[1] * c[2] - b[2] * c[1]) + b[0] * (c[1] * a[2] - c[2] * a[1]) + c[0] * (a[1] * b[2] - a[2] * b[1]); + } + return vol / 6.0; +} + +//////////////////////////////////////////////////////////////////////////////// +/// BuildBVH + +void O2Tessellated::BuildBVH() +{ + using Scalar = float; + using BBox = bvh::v2::BBox; + using Vec3 = bvh::v2::Vec; + using Node = bvh::v2::Node; + using Bvh = bvh::v2::Bvh; + + // helper determining axis aligned bounding box from a facet; + auto GetBoundingBox = [this](TGeoFacet const& facet) { +#ifndef NDEBUG + const auto nvertices = facet.GetNvert(); + assert(nvertices == 3); // for now only triangles +#endif + const auto& v1 = fVertices[facet[0]]; + const auto& v2 = fVertices[facet[1]]; + const auto& v3 = fVertices[facet[2]]; + BBox bbox; + bbox.min[0] = std::min(std::min(v1[0], v2[0]), v3[0]) - 0.001f; + bbox.min[1] = std::min(std::min(v1[1], v2[1]), v3[1]) - 0.001f; + bbox.min[2] = std::min(std::min(v1[2], v2[2]), v3[2]) - 0.001f; + bbox.max[0] = std::max(std::max(v1[0], v2[0]), v3[0]) + 0.001f; + bbox.max[1] = std::max(std::max(v1[1], v2[1]), v3[1]) + 0.001f; + bbox.max[2] = std::max(std::max(v1[2], v2[2]), v3[2]) + 0.001f; + return bbox; + }; + + // we need bounding boxes enclosing the primitives and centers of primitives + // (replaced here by centers of bounding boxes) to build the bvh + std::vector bboxes; + std::vector centers; + + // loop over all the triangles/Facets; + int nd = fFacets.size(); + for (int i = 0; i < nd; ++i) { + auto& facet = fFacets[i]; + + // fetch the bounding box of this node and add to the vector of bounding boxes + (bboxes).push_back(GetBoundingBox(facet)); + centers.emplace_back((bboxes).back().get_center()); + } + + // check if some previous object is registered and delete if necessary + if (fBVH) { + delete (Bvh*)fBVH; + fBVH = nullptr; + } + + // create the bvh + typename bvh::v2::DefaultBuilder::Config config; + config.quality = bvh::v2::DefaultBuilder::Quality::High; + auto bvh = bvh::v2::DefaultBuilder::build(bboxes, centers, config); + auto bvhptr = new Bvh; + *bvhptr = std::move(bvh); // copy structure + fBVH = (void*)(bvhptr); + + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Contains + +bool O2Tessellated::Contains(Double_t const* point) const +{ + // we do the parity test + using Scalar = float; + using Vec3 = bvh::v2::Vec; + using Node = bvh::v2::Node; + using Bvh = bvh::v2::Bvh; + using Ray = bvh::v2::Ray; + + // let's fetch the bvh + auto mybvh = (Bvh*)fBVH; + if (!mybvh) { + assert(false); + return false; + } + + auto truncate_roundup = [](double orig) { + float epsilon = std::numeric_limits::epsilon() * std::fabs(orig); + // Add the bias to x before assigning it to y + return static_cast(orig + epsilon); + }; + + // let's do very quick checks against the top node + if (!TGeoBBox::Contains(point)) { + return false; + } + + // An arbitrary test direction. + // Doesn't need to be normalized and probes all normals. Also ensuring to be skewed somewhat + // without evident symmetries. + Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757}; + + double local_step = Big(); + // the ray used for bvh interaction + Ray ray(Vec3(point[0], point[1], point[2]), // origin + Vec3(test_dir[0], test_dir[1], test_dir[2]), // direction + 0.0f, // minimum distance (could give stepmax ?) + truncate_roundup(local_step)); + + static constexpr bool use_robust_traversal = true; + + // Traverse the BVH and apply concrete object intersection in BVH leafs + bvh::v2::GrowingStack stack; + size_t crossings = 0; + mybvh->intersect(ray, mybvh->get_root().index, stack, [&](size_t begin, size_t end) { + for (size_t prim_id = begin; prim_id < end; ++prim_id) { + auto objectid = mybvh->prim_ids[prim_id]; + auto& facet = fFacets[objectid]; + + // for the parity test, we probe all crossing surfaces + const auto& v0 = fVertices[facet[0]]; + const auto& v1 = fVertices[facet[1]]; + const auto& v2 = fVertices[facet[2]]; + + const double t = rayTriangle(Vertex_t(point[0], point[1], point[2]), + test_dir, v0, v1, v2, 0.); + + if (t != std::numeric_limits::infinity()) { + ++crossings; + } + } + return false; + }); + + return crossings & 1; +} + +namespace +{ + +// Helper classes/structs used for priority queue - BVH traversal +// structure keeping cost (value) for a BVH index +struct BVHPrioElement { + size_t bvh_node_id; + float value; +}; + +// A priority queue for BVHPrioElement with an additional clear method +// for quick reset. We intentionally derive from std::priority_queue here to expose a +// clear() convenience method via access to the protected container `c`. +// This is internal, non-polymorphic code and relies on standard-library +// implementation details that are stable across supported platforms. +template +class BVHPrioQueue : public std::priority_queue, Comparator> +{ + public: + using std::priority_queue, + Comparator>::priority_queue; // constructor inclusion + + // convenience method to quickly clear/reset the queue (instead of having to pop one by one) + void clear() { this->c.clear(); } +}; + +} // namespace + +/// a reusable safety kernel, which optionally returns the closest face +template +inline Double_t O2Tessellated::SafetyKernel(const Double_t* point, bool in, int* closest_facet_id) const +{ + // This is the classic traversal/pruning of a BVH based on priority queue search + + float smallest_safety_sq = TGeoShape::Big(); + + using Scalar = float; + using Vec3 = bvh::v2::Vec; + using Node = bvh::v2::Node; + using Bvh = bvh::v2::Bvh; + + // let's fetch the bvh + auto mybvh = (Bvh*)fBVH; + + // testpoint object in float for quick BVH interaction + Vec3 testpoint(point[0], point[1], point[2]); + + auto currnode = mybvh->nodes[0]; // we start from the top BVH node + // we do a quick check on the top node (in case we are outside shape) + bool outside_top = false; + if (!in) { + outside_top = !bvh::v2::extra::contains(currnode.get_bbox(), testpoint); + if (outside_top) { + const auto safety_sq_to_top = bvh::v2::extra::SafetySqToNode(currnode.get_bbox(), testpoint); + // we simply return safety to the outer bounding box as an estimate + return std::sqrt(safety_sq_to_top); + } + } + + // comparator bringing out "smallest" value on top + auto cmp = [](BVHPrioElement a, BVHPrioElement b) { return a.value > b.value; }; + static thread_local BVHPrioQueue queue(cmp); + queue.clear(); + + // algorithm is based on standard iterative tree traversal with priority queues + float current_safety_to_node_sq = 0.f; + + if (returnFace) { + *closest_facet_id = -1; + } + + do { + if (currnode.is_leaf()) { + // we are in a leaf node and actually talk to a face/triangular primitive + const auto begin_prim_id = currnode.index.first_id(); + const auto end_prim_id = begin_prim_id + currnode.index.prim_count(); + + for (auto p_id = begin_prim_id; p_id < end_prim_id; p_id++) { + const auto object_id = mybvh->prim_ids[p_id]; + + const auto& facet = fFacets[object_id]; + const auto& v1 = fVertices[facet[0]]; + const auto& v2 = fVertices[facet[1]]; + const auto& v3 = fVertices[facet[2]]; + + auto thissafetySQ = pointTriangleDistSq(Vec3f{point[0], point[1], point[2]}, Vec3f{v1[0], v1[1], v1[2]}, + Vec3f{v2[0], v2[1], v2[2]}, Vec3f{v3[0], v3[1], v3[2]}); + + if (thissafetySQ < smallest_safety_sq) { + smallest_safety_sq = thissafetySQ; + if (returnFace) { + *closest_facet_id = object_id; + } + } + } + } else { + // not a leave node ... for further traversal, + // we inject the children into priority queue based on distance to it's bounding box + const auto leftchild_id = currnode.index.first_id(); + const auto rightchild_id = leftchild_id + 1; + + for (size_t childid : {leftchild_id, rightchild_id}) { + if (childid >= mybvh->nodes.size()) { + continue; + } + + const auto& node = mybvh->nodes[childid]; + const auto inside = bvh::v2::extra::contains(node.get_bbox(), testpoint); + + if (inside) { + // this must be further considered because we are inside the bounding box + queue.push(BVHPrioElement{childid, -1.}); + } else { + auto safety_to_node_square = bvh::v2::extra::SafetySqToNode(node.get_bbox(), testpoint); + if (safety_to_node_square <= smallest_safety_sq) { + // this should be further considered + queue.push(BVHPrioElement{childid, safety_to_node_square}); + } + } + } + } + + if (queue.size() > 0) { + auto currElement = queue.top(); + currnode = mybvh->nodes[currElement.bvh_node_id]; + current_safety_to_node_sq = currElement.value; + queue.pop(); + } else { + break; + } + } while (current_safety_to_node_sq <= smallest_safety_sq); + + return std::nextafter(std::sqrt(smallest_safety_sq), 0.0f); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Safety + +Double_t O2Tessellated::Safety(const Double_t* point, Bool_t in) const +{ + // we could use some caching here (in future) since queries to the solid will likely + // be made with some locality + + // fall-back to precise safety kernel + return SafetyKernel(point, in); +} + +//////////////////////////////////////////////////////////////////////////////// +/// ComputeNormal interface + +void O2Tessellated::ComputeNormal(const Double_t* point, const Double_t* dir, Double_t* norm) const +{ + // We take the approach to identify closest facet to the point via safety + // and returning the normal from this face. + + // TODO: Before doing that we could check for cached points from other queries + + // use safety kernel + int closest_face_id = -1; + SafetyKernel(point, true, &closest_face_id); + + if (closest_face_id < 0) { + norm[0] = 1.; + norm[1] = 0.; + norm[2] = 0.; + return; + } + + const auto& n = fOutwardNormals[closest_face_id]; + norm[0] = n[0]; + norm[1] = n[1]; + norm[2] = n[2]; + + // change sign depending on dir + if (norm[0] * dir[0] + norm[1] * dir[1] + norm[2] * dir[2] < 0) { + norm[0] = -norm[0]; + norm[1] = -norm[1]; + norm[2] = -norm[2]; + } + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// trivial (non-BVH) DistFromInside function + +Double_t O2Tessellated::DistFromInside_Loop(const Double_t* point, const Double_t* dir) const +{ + Vertex_t p(point[0], point[1], point[2]); + Vertex_t d(dir[0], dir[1], dir[2]); + + double dist = Big(); + for (size_t i = 0; i < fFacets.size(); ++i) { + const auto& facet = fFacets[i]; + const auto& n = fOutwardNormals[i]; + + // Only exiting surfaces are relevant (from inside--> dot product must be positive) + if (n.Dot(d) <= 0.0) { + continue; + } + + const auto& v0 = fVertices[facet[0]]; + const auto& v1 = fVertices[facet[1]]; + const auto& v2 = fVertices[facet[2]]; + + const double t = rayTriangle(p, d, v0, v1, v2, 0.); + + if (t < dist) { + dist = t; + } + } + return dist; +} + +//////////////////////////////////////////////////////////////////////////////// +/// trivial (non-BVH) DistFromOutside function + +Double_t O2Tessellated::DistFromOutside_Loop(const Double_t* point, const Double_t* dir) const +{ + Vertex_t p(point[0], point[1], point[2]); + Vertex_t d(dir[0], dir[1], dir[2]); + + double dist = Big(); + for (size_t i = 0; i < fFacets.size(); ++i) { + const auto& facet = fFacets[i]; + const auto& n = fOutwardNormals[i]; + + // Only exiting surfaces are relevant (from outside, the dot product must be negative) + if (n.Dot(d) > 0.0) { + continue; + } + + const auto& v0 = fVertices[facet[0]]; + const auto& v1 = fVertices[facet[1]]; + const auto& v2 = fVertices[facet[2]]; + + const double t = rayTriangle(p, d, v0, v1, v2, 0.); + + if (t < dist) { + dist = t; + } + } + return dist; +} + +//////////////////////////////////////////////////////////////////////////////// +/// trivial (non-BVH) Contains + +bool O2Tessellated::Contains_Loop(const Double_t* point) const +{ + // Fixed ray direction + const Vertex_t test_dir{1.0, 1.41421356237, 1.73205080757}; + + Vertex_t p(point[0], point[1], point[2]); + + int crossings = 0; + for (size_t i = 0; i < fFacets.size(); ++i) { + const auto& facet = fFacets[i]; + + const auto& v0 = fVertices[facet[0]]; + const auto& v1 = fVertices[facet[1]]; + const auto& v2 = fVertices[facet[2]]; + + const double t = rayTriangle(p, test_dir, v0, v1, v2, 0.); + if (t != std::numeric_limits::infinity()) { + ++crossings; + } + } + return (crossings & 1); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Custom streamer which performs Closing on read. +/// Recalculation of BVH and normals is fast + +void O2Tessellated::Streamer(TBuffer& b) +{ + if (b.IsReading()) { + b.ReadClassBuffer(O2Tessellated::Class(), this); + CloseShape(false); // close shape but do not re-perform checks + } else { + b.WriteClassBuffer(O2Tessellated::Class(), this); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Calculate the normals + +void O2Tessellated::CalculateNormals() +{ + fOutwardNormals.clear(); + for (auto& facet : fFacets) { + auto& v1 = fVertices[facet[0]]; + auto& v2 = fVertices[facet[1]]; + auto& v3 = fVertices[facet[2]]; + using Vec3d = Vec3f; + auto norm = triangleNormal(Vec3d{v1[0], v1[1], v1[2]}, Vec3d{v2[0], v2[1], v2[2]}, Vec3d{v3[0], v3[1], v3[2]}); + fOutwardNormals.emplace_back(Vertex_t{norm.x, norm.y, norm.z}); + } +} + +// NOLINTEND \ No newline at end of file diff --git a/Detectors/Base/src/Propagator.cxx b/Detectors/Base/src/Propagator.cxx index 1c44cea65c69c..208b9bf138688 100644 --- a/Detectors/Base/src/Propagator.cxx +++ b/Detectors/Base/src/Propagator.cxx @@ -15,6 +15,7 @@ #include "GPUCommonMath.h" #include "GPUTPCGMPolynomialField.h" #include "MathUtils/Utils.h" +#include "ReconstructionDataFormats/HelixHelper.h" #include "ReconstructionDataFormats/Vertex.h" using namespace o2::base; @@ -217,6 +218,75 @@ GPUd() bool PropagatorImpl::PropagateToXBxByBz(TrackParCov_t& track, va return true; } +//_______________________________________________________________________ +template +GPUd() bool PropagatorImpl::PropagateToXBxByBz(TrackParCov_t& track, TrackPar_t& linRef, value_type xToGo, value_type maxSnp, value_type maxStep, + PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral* tofInfo, int signCorr) const +{ + //---------------------------------------------------------------- + // + // Propagates the track to the plane X=xk (cm), using linRef as a Kalman linearisation point. + // taking into account all the three components of the magnetic field + // and correcting for the crossed material. + // + // maxStep - maximal step for propagation + // tofInfo - optional container for track length and PID-dependent TOF integration + // + // matCorr - material correction type, it is up to the user to make sure the pointer is attached (if LUT is requested) + //---------------------------------------------------------------- + auto dx = xToGo - track.getX(); + int dir = dx > 0.f ? 1 : -1; + if (!signCorr) { + signCorr = -dir; // sign of eloss correction is not imposed + } + + std::array b{}; + while (math_utils::detail::abs(dx) > Epsilon) { + auto step = math_utils::detail::min(math_utils::detail::abs(dx), maxStep); + if (dir < 0) { + step = -step; + } + auto x = track.getX() + step; + auto xyz0 = linRef.getXYZGlo(); + getFieldXYZ(xyz0, &b[0]); + + auto correct = [&track, &linRef, &xyz0, tofInfo, matCorr, signCorr, this]() { + bool res = true; + if (matCorr != MatCorrType::USEMatCorrNONE) { + auto xyz1 = linRef.getXYZGlo(); + auto mb = this->getMatBudget(matCorr, xyz0, xyz1); + if (!track.correctForMaterial(linRef, mb.meanX2X0, mb.getXRho(signCorr))) { + res = false; + } + if (tofInfo) { + tofInfo->addStep(mb.length, linRef.getQ2P2()); // fill L,ToF info using already calculated step length + tofInfo->addX2X0(mb.meanX2X0); + tofInfo->addXRho(mb.getXRho(signCorr)); + } + } else if (tofInfo) { // if tofInfo filling was requested w/o material correction, we need to calculate the step lenght + auto xyz1 = linRef.getXYZGlo(); + math_utils::Vector3D stepV(xyz1.X() - xyz0.X(), xyz1.Y() - xyz0.Y(), xyz1.Z() - xyz0.Z()); + tofInfo->addStep(stepV.R(), linRef.getQ2P2()); + } + return res; + }; + + if (!track.propagateTo(x, linRef, b)) { + return false; + } + if (maxSnp > 0 && math_utils::detail::abs(track.getSnp()) >= maxSnp) { + correct(); + return false; + } + if (!correct()) { + return false; + } + dx = xToGo - track.getX(); + } + track.setX(xToGo); + return true; +} + //_______________________________________________________________________ template GPUd() bool PropagatorImpl::PropagateToXBxByBz(TrackPar_t& track, value_type xToGo, value_type maxSnp, value_type maxStep, @@ -294,8 +364,7 @@ GPUd() bool PropagatorImpl::propagateToX(TrackParCov_t& track, value_ty //---------------------------------------------------------------- // // Propagates the track to the plane X=xk (cm) - // taking into account all the three components of the magnetic field - // and correcting for the crossed material. + // Use bz only and correct for the crossed material. // // maxStep - maximal step for propagation // tofInfo - optional container for track length and PID-dependent TOF integration @@ -351,6 +420,72 @@ GPUd() bool PropagatorImpl::propagateToX(TrackParCov_t& track, value_ty return true; } +//_______________________________________________________________________ +template +GPUd() bool PropagatorImpl::propagateToX(TrackParCov_t& track, TrackPar_t& linRef, value_type xToGo, value_type bZ, value_type maxSnp, value_type maxStep, + PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral* tofInfo, int signCorr) const +{ + //---------------------------------------------------------------- + // + // Propagates the track to the plane X=xk (cm), using linRef as a Kalman linearisation point. + // Use bz only and correct for the crossed material if requested. + // + // maxStep - maximal step for propagation + // tofInfo - optional container for track length and PID-dependent TOF integration + // + // matCorr - material correction type, it is up to the user to make sure the pointer is attached (if LUT is requested) + //---------------------------------------------------------------- + auto dx = xToGo - track.getX(); + int dir = dx > 0.f ? 1 : -1; + if (!signCorr) { + signCorr = -dir; // sign of eloss correction is not imposed + } + + while (math_utils::detail::abs(dx) > Epsilon) { + auto step = math_utils::detail::min(math_utils::detail::abs(dx), maxStep); + if (dir < 0) { + step = -step; + } + auto x = track.getX() + step; + auto xyz0 = linRef.getXYZGlo(); + + auto correct = [&track, &linRef, &xyz0, tofInfo, matCorr, signCorr, this]() { + bool res = true; + if (matCorr != MatCorrType::USEMatCorrNONE) { + auto xyz1 = linRef.getXYZGlo(); + auto mb = this->getMatBudget(matCorr, xyz0, xyz1); + if (!track.correctForMaterial(linRef, mb.meanX2X0, mb.getXRho(signCorr))) { + res = false; + } + if (tofInfo) { + tofInfo->addStep(mb.length, linRef.getQ2P2()); // fill L,ToF info using already calculated step length + tofInfo->addX2X0(mb.meanX2X0); + tofInfo->addXRho(mb.getXRho(signCorr)); + } + } else if (tofInfo) { // if tofInfo filling was requested w/o material correction, we need to calculate the step lenght + auto xyz1 = linRef.getXYZGlo(); + math_utils::Vector3D stepV(xyz1.X() - xyz0.X(), xyz1.Y() - xyz0.Y(), xyz1.Z() - xyz0.Z()); + tofInfo->addStep(stepV.R(), linRef.getQ2P2()); + } + return res; + }; + + if (!track.propagateTo(x, linRef, bZ)) { // linRef also updated + return false; + } + if (maxSnp > 0 && math_utils::detail::abs(track.getSnp()) >= maxSnp) { + correct(); + return false; + } + if (!correct()) { + return false; + } + dx = xToGo - track.getX(); + } + track.setX(xToGo); + return true; +} + //_______________________________________________________________________ template GPUd() bool PropagatorImpl::propagateToX(TrackPar_t& track, value_type xToGo, value_type bZ, value_type maxSnp, value_type maxStep, @@ -418,6 +553,118 @@ GPUd() bool PropagatorImpl::propagateToX(TrackPar_t& track, value_type return true; } +//_______________________________________________________________________ +template +template +GPUd() bool PropagatorImpl::propagateToR(track_T& track, value_type r, bool bzOnly, value_type maxSnp, value_type maxStep, + MatCorrType matCorr, track::TrackLTIntegral* tofInfo, int signCorr) const +{ + const value_T MaxPhiLoc = math_utils::detail::asin(maxSnp), MaxPhiLocSafe = 0.95 * MaxPhiLoc; + auto bz = getNominalBz(); + if (math_utils::detail::abs(bz) > constants::math::Almost0) { + o2::track::TrackAuxPar traux(track, bz); + o2::track::TrackAuxPar crad; + value_type r0 = math_utils::detail::sqrt(track.getX() * track.getX() + track.getY() * track.getY()); + value_type dr = (r - r0); + value_type rTmp = r - (math_utils::detail::abs(dr) > 1. ? (dr > 0 ? 0.5 : -0.5) : 0.5 * dr); // 1st propagate a few mm short of the targer R + crad.rC = rTmp; + crad.c = crad.cc = 1.f; + crad.s = crad.ss = crad.cs = 0.f; + o2::track::CrossInfo cross; + cross.circlesCrossInfo(crad, traux, 0.); + if (cross.nDCA < 1) { + return false; + } + double phiCross[2] = {}, dphi[2] = {}; + auto curv = track.getCurvature(bz); + bool clockwise = curv < 0; // q+ in B+ or q- in B- goes clockwise + auto phiLoc = math_utils::detail::asin(track.getSnp()); + auto phi0 = phiLoc + track.getAlpha(); + o2::math_utils::detail::bringTo02Pi(phi0); + for (int i = 0; i < cross.nDCA; i++) { + // track pT direction angle at crossing points: + // == angle of the tangential to track circle at the crossing point X,Y + // == normal to the radial vector from the track circle center {X-cX, Y-cY} + // i.e. the angle of the vector {Y-cY, -(X-cx)} + auto normX = double(cross.yDCA[i]) - double(traux.yC), normY = -(double(cross.xDCA[i]) - double(traux.xC)); + if (!clockwise) { + normX = -normX; + normY = -normY; + } + phiCross[i] = math_utils::detail::atan2(normY, normX); + o2::math_utils::detail::bringTo02Pi(phiCross[i]); + dphi[i] = phiCross[i] - phi0; + if (dphi[i] > o2::constants::math::PI) { + dphi[i] -= o2::constants::math::TwoPI; + } else if (dphi[i] < -o2::constants::math::PI) { + dphi[i] += o2::constants::math::TwoPI; + } + } + int sel = cross.nDCA == 1 ? 0 : (clockwise ? (dphi[0] < dphi[1] ? 0 : 1) : (dphi[1] < dphi[0] ? 0 : 1)); + auto deltaPhi = dphi[sel]; + + while (1) { + auto phiLocFin = phiLoc + deltaPhi; + // case1 + if (math_utils::detail::abs(phiLocFin) < MaxPhiLocSafe) { // just 1 step propagation + auto deltaX = (math_utils::detail::sin(phiLocFin) - track.getSnp()) / track.getCurvature(bz); + if (!propagateTo(track, track.getX() + deltaX, bzOnly, maxSnp, maxStep, matCorr, tofInfo, signCorr)) { + return false; + } + break; + } + if (math_utils::detail::abs(deltaPhi) < (2 * MaxPhiLocSafe)) { // still can go in 1 step with one extra rotation + auto rot = phiLoc + 0.5 * deltaPhi; + if (!track.rotate(track.getAlpha() + rot)) { + return false; + } + phiLoc -= rot; + continue; // should be ok for the case 1 now. + } + + auto rot = phiLoc + (deltaPhi > 0 ? MaxPhiLocSafe : -MaxPhiLocSafe); + if (!track.rotate(track.getAlpha() + rot)) { + return false; + } + phiLoc -= rot; // = +- MaxPhiLocSafe + + // propagate to phiLoc = +-MaxPhiLocSafe + auto tgtPhiLoc = deltaPhi > 0 ? MaxPhiLocSafe : -MaxPhiLocSafe; + auto deltaX = (math_utils::detail::sin(tgtPhiLoc) - track.getSnp()) / track.getCurvature(bz); + if (!propagateTo(track, track.getX() + deltaX, bzOnly, maxSnp, maxStep, matCorr, tofInfo, signCorr)) { + return false; + } + deltaPhi -= tgtPhiLoc - phiLoc; + phiLoc = deltaPhi > 0 ? MaxPhiLocSafe : -MaxPhiLocSafe; + continue; // should be of for the case 1 now. + } + bz = getBz(math_utils::Point3D{value_type(cross.xDCA[sel]), value_type(cross.yDCA[sel]), value_type(track.getZ())}); + } + // do final step till target R, also covers Bz = 0; + value_type xfin; + if (!track.getXatLabR(r, xfin, bz)) { + return false; + } + return propagateToX(track, xfin, bzOnly, maxSnp, maxStep, matCorr, tofInfo, signCorr); +} + +template +GPUd() bool PropagatorImpl::propagateToAlphaX(TrackParCov_t& track, TrackPar_t* linRef, value_type alpha, value_type x, bool bzOnly, value_type maxSnp, value_type maxStep, int minSteps, + MatCorrType matCorr, track::TrackLTIntegral* tofInfo, int signCorr) const +{ + // propagate to alpha,X, if needed in a few steps + auto snp = track.getSnpAt(alpha, x, getNominalBz()); + // apply safety factor 0.9 for crude rotation estimate + if (math_utils::detail::abs(snp) < maxSnp * 0.9 && (linRef ? track.rotate(alpha, *linRef, getNominalBz()) : track.rotate(alpha))) { + auto dx = math_utils::detail::abs(x - track.getX()); + if (dx < Epsilon) { + return true; + } + return propagateTo(track, linRef, x, bzOnly, maxSnp, math_utils::detail::min(dx / minSteps, maxStep), matCorr, tofInfo, signCorr); + } + return false; +} + //_______________________________________________________________________ template template @@ -468,6 +715,10 @@ GPUd() bool PropagatorImpl::propagateToDCA(const o2::dataformats::Verte // Estimate the impact parameter neglecting the track curvature value_type d = math_utils::detail::abs(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } value_type crv = track.getCurvature(bZ); @@ -488,6 +739,10 @@ GPUd() bool PropagatorImpl::propagateToDCA(const o2::dataformats::Verte #elif !defined(GPUCA_NO_FMT) LOG(debug) << "failed to propagate to alpha=" << alp << " X=" << xv << vtx; #endif + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } track = tmpT; @@ -517,6 +772,10 @@ GPUd() bool PropagatorImpl::propagateToDCABxByBz(const o2::dataformats: // Estimate the impact parameter neglecting the track curvature value_type d = math_utils::detail::abs(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } value_type crv = track.getCurvature(mNominalBz); @@ -537,6 +796,10 @@ GPUd() bool PropagatorImpl::propagateToDCABxByBz(const o2::dataformats: #elif !defined(GPUCA_NO_FMT) LOG(debug) << "failed to propagate to alpha=" << alp << " X=" << xv << vtx; #endif + if (dca) { // provide default DCA for failed propag + dca->set(o2::track::DefaultDCA, o2::track::DefaultDCA, + o2::track::DefaultDCACov, o2::track::DefaultDCACov, o2::track::DefaultDCACov); + } return false; } track = tmpT; @@ -566,6 +829,10 @@ GPUd() bool PropagatorImpl::propagateToDCA(const math_utils::Point3D(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + (*dca)[0] = o2::track::DefaultDCA; + (*dca)[1] = o2::track::DefaultDCA; + } return false; } value_type crv = track.getCurvature(bZ); @@ -587,6 +854,10 @@ GPUd() bool PropagatorImpl::propagateToDCA(const math_utils::Point3D::propagateToDCABxByBz(const math_utils::Poin // Estimate the impact parameter neglecting the track curvature value_type d = math_utils::detail::abs(x * snp - y * csp); if (d > maxD) { + if (dca) { // provide default DCA for failed propag + (*dca)[0] = o2::track::DefaultDCA; + (*dca)[1] = o2::track::DefaultDCA; + } return false; } value_type crv = track.getCurvature(mNominalBz); @@ -635,6 +910,10 @@ GPUd() bool PropagatorImpl::propagateToDCABxByBz(const math_utils::Poin #else LOG(debug) << "failed to propagate to alpha=" << alp << " X=" << xv << " for vertex " << vtx.X() << ' ' << vtx.Y() << ' ' << vtx.Z(); #endif + if (dca) { // provide default DCA for failed propag + (*dca)[0] = o2::track::DefaultDCA; + (*dca)[1] = o2::track::DefaultDCA; + } return false; } track = tmpT; @@ -772,6 +1051,35 @@ GPUd() void PropagatorImpl::getFieldXYZImpl(const math_utils::Point3D +template +GPUd() T PropagatorImpl::getBzImpl(const math_utils::Point3D xyz) const +{ + T bz = 0; + if (mGPUField) { +#if defined(GPUCA_GPUCODE_DEVICE) && defined(GPUCA_HAS_GLOBAL_SYMBOL_CONSTANT_MEM) + const auto* f = &GPUCA_CONSMEM.param.polynomialField; // Access directly from constant memory on GPU (copied here to avoid complicated header dependencies) +#else + const auto* f = mGPUField; +#endif + constexpr value_type kCLight1 = 1. / o2::gpu::gpu_common_constants::kCLight; + bz = f->GetFieldBz(xyz.X(), xyz.Y(), xyz.Z()) * kCLight1; + } else { +#ifndef GPUCA_GPUCODE + if (mFieldFast) { + mFieldFast->GetBz(xyz, bz); // Must not call the host-only function in GPU compilation + } else { +#ifdef GPUCA_STANDALONE + LOG(fatal) << "Normal field cannot be used in standalone benchmark"; +#else + bz = mField->GetBz(xyz.X(), xyz.Y(), xyz.Z()); +#endif + } +#endif + } + return bz; +} + template GPUd() void PropagatorImpl::getFieldXYZ(const math_utils::Point3D xyz, float* bxyz) const { @@ -784,12 +1092,26 @@ GPUd() void PropagatorImpl::getFieldXYZ(const math_utils::Point3D(xyz, bxyz); } +template +GPUd() float PropagatorImpl::getBz(const math_utils::Point3D xyz) const +{ + return getBzImpl(xyz); +} + +template +GPUd() double PropagatorImpl::getBz(const math_utils::Point3D xyz) const +{ + return getBzImpl(xyz); +} + namespace o2::base { #if !defined(GPUCA_GPUCODE) || defined(GPUCA_GPUCODE_DEVICE) // FIXME: DR: WORKAROUND to avoid CUDA bug creating host symbols for device code. template class PropagatorImpl; template bool GPUdni() PropagatorImpl::propagateToAlphaX::TrackPar_t>(PropagatorImpl::TrackPar_t&, float, float, bool, float, float, int, PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral*, int) const; template bool GPUdni() PropagatorImpl::propagateToAlphaX::TrackParCov_t>(PropagatorImpl::TrackParCov_t&, float, float, bool, float, float, int, PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral*, int) const; +template bool GPUdni() PropagatorImpl::propagateToR::TrackPar_t>(PropagatorImpl::TrackPar_t&, float, bool, float, float, PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral*, int) const; +template bool GPUdni() PropagatorImpl::propagateToR::TrackParCov_t>(PropagatorImpl::TrackParCov_t&, float, bool, float, float, PropagatorImpl::MatCorrType matCorr, track::TrackLTIntegral*, int) const; #endif #ifndef GPUCA_GPUCODE template class PropagatorImpl; 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/src/TGeoGeometryUtils.cxx b/Detectors/Base/src/TGeoGeometryUtils.cxx new file mode 100644 index 0000000000000..6f06eff17a6d7 --- /dev/null +++ b/Detectors/Base/src/TGeoGeometryUtils.cxx @@ -0,0 +1,144 @@ +// 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 TGeoGeometryUtils.cxx +/// \author Sandro Wenzel (CERN) +/// \brief Collection of utility functions for TGeo + +#include +#include +#include +#include +#include + +namespace o2 +{ +namespace base +{ + +namespace +{ +// some helpers to interpret TGeo TBuffer3D output +// and convert it to surface triangles (reengineered from TGeo code) + +std::vector BuildVertexLoop(const TBuffer3D& buf, + const std::vector& segs) +{ + // adjacency list + std::unordered_map> adj; + + for (int s : segs) { + int a = buf.fSegs[3 * s + 1]; + int b = buf.fSegs[3 * s + 2]; + adj[a].push_back(b); + adj[b].push_back(a); + } + + // start from any vertex + int start = adj.begin()->first; + int prev = -1; + int curr = start; + + std::vector loop; + + while (true) { + loop.push_back(curr); + + const auto& nbrs = adj[curr]; + int next = -1; + + for (int n : nbrs) { + if (n != prev) { + next = n; + break; + } + } + + if (next == -1 || next == start) { + break; + } + + prev = curr; + curr = next; + } + return loop; +} + +std::vector> ExtractPolygons(const TBuffer3D& buf) +{ + std::vector> polys; + Int_t idx = 0; + + for (Int_t ip = 0; ip < buf.NbPols(); ++ip) { + + idx++; // color + Int_t nseg = buf.fPols[idx++]; + + std::vector segs(nseg); + for (Int_t i = 0; i < nseg; ++i) { + segs[i] = buf.fPols[idx++]; + } + + auto verts = BuildVertexLoop(buf, segs); + if (verts.size() >= 3) { + polys.push_back(std::move(verts)); + } + } + + return polys; +} + +std::vector> + Triangulate(const std::vector>& polys) +{ + std::vector> tris; + for (const auto& poly : polys) { + int nv = poly.size(); + if (nv < 3) { + continue; + } + + int v0 = poly[0]; + for (int i = 1; i < nv - 1; ++i) { + tris.push_back({{v0, poly[i], poly[i + 1]}}); + } + } + return tris; +} + +TGeoTessellated* MakeTessellated(const TBuffer3D& buf) +{ + auto polys = ExtractPolygons(buf); + auto tris = Triangulate(polys); + int i = 0; + auto* tess = new TGeoTessellated("tess"); + const Double_t* p = buf.fPnts; + for (auto& t : tris) { + tess->AddFacet( + TGeoTessellated::Vertex_t{p[3 * t[0]], p[3 * t[0] + 1], p[3 * t[0] + 2]}, + TGeoTessellated::Vertex_t{p[3 * t[1]], p[3 * t[1] + 1], p[3 * t[1] + 2]}, + TGeoTessellated::Vertex_t{p[3 * t[2]], p[3 * t[2] + 1], p[3 * t[2] + 2]}); + } + tess->CloseShape(); + return tess; +} +} // end anonymous namespace + +///< Transform any (primitive) TGeoShape to a TGeoTessellated +TGeoTessellated* TGeoGeometryUtils::TGeoShapeToTGeoTessellated(TGeoShape const* shape) +{ + auto& buf = shape->GetBuffer3D(TBuffer3D::kRawSizes | TBuffer3D::kRaw | TBuffer3D::kCore, false); + auto tes = MakeTessellated(buf); + return tes; +} + +} // namespace base +} // namespace o2 diff --git a/Detectors/Base/src/bvh2_extra_kernels.h b/Detectors/Base/src/bvh2_extra_kernels.h new file mode 100644 index 0000000000000..70e43202a53c4 --- /dev/null +++ b/Detectors/Base/src/bvh2_extra_kernels.h @@ -0,0 +1,79 @@ +// 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. + +// Sandro Wenzel 2026 + +#ifndef ROOT_GEOM_BVH2_EXTRA + +namespace bvh::v2::extra +{ + +// reusable geometry kernels used in multiple places +// for interaction with BVH2 structures + +// determines if a point is inside the bounding box +template +bool contains(bvh::v2::BBox const& box, bvh::v2::Vec const& p) +{ + auto min = box.min; + auto max = box.max; + return (p[0] >= min[0] && p[0] <= max[0]) && (p[1] >= min[1] && p[1] <= max[1]) && + (p[2] >= min[2] && p[2] <= max[2]); +} + +// determines the largest squared distance of point to any of the bounding box corners +template +auto RmaxSqToNode(bvh::v2::BBox const& box, bvh::v2::Vec const& p) +{ + // construct the 8 corners to get the maximal distance + const auto minCorner = box.min; + const auto maxCorner = box.max; + using Vec3 = bvh::v2::Vec; + // these are the corners of the bounding box + const std::array, 8> corners{ + Vec3{minCorner[0], minCorner[1], minCorner[2]}, Vec3{minCorner[0], minCorner[1], maxCorner[2]}, + Vec3{minCorner[0], maxCorner[1], minCorner[2]}, Vec3{minCorner[0], maxCorner[1], maxCorner[2]}, + Vec3{maxCorner[0], minCorner[1], minCorner[2]}, Vec3{maxCorner[0], minCorner[1], maxCorner[2]}, + Vec3{maxCorner[0], maxCorner[1], minCorner[2]}, Vec3{maxCorner[0], maxCorner[1], maxCorner[2]}}; + + T Rmax_sq{0}; + for (const auto& corner : corners) { + float R_sq = 0.; + const auto dx = corner[0] - p[0]; + R_sq += dx * dx; + const auto dy = corner[1] - p[1]; + R_sq += dy * dy; + const auto dz = corner[2] - p[2]; + R_sq += dz * dz; + Rmax_sq = std::max(Rmax_sq, R_sq); + } + return Rmax_sq; +}; + +// determines the minimum squared distance of point to a bounding box ("safey square") +template +auto SafetySqToNode(bvh::v2::BBox const& box, bvh::v2::Vec const& p) +{ + T sqDist{0.0}; + for (int i = 0; i < 3; i++) { + T v = p[i]; + if (v < box.min[i]) { + sqDist += (box.min[i] - v) * (box.min[i] - v); + } else if (v > box.max[i]) { + sqDist += (v - box.max[i]) * (v - box.max[i]); + } + } + return sqDist; +}; + +} // namespace bvh::v2::extra + +#endif \ No newline at end of file diff --git a/Detectors/Base/src/bvh2_third_party.h b/Detectors/Base/src/bvh2_third_party.h new file mode 100644 index 0000000000000..5cf7772269642 --- /dev/null +++ b/Detectors/Base/src/bvh2_third_party.h @@ -0,0 +1,49 @@ +// 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. + +// Sandro Wenzel 2026 + +#ifndef ROOT_GEOM_BVH2_THIRD_PARTY + +// A single entry header into third-party BVH2 installed in ROOT +// Good place to manage compiler warnings etc. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wpsabi" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wpsabi" +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wattributes" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 5051) +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif \ No newline at end of file diff --git a/Detectors/Base/test/buildMatBudLUT.C b/Detectors/Base/test/buildMatBudLUT.C index 800597d6166fd..860fcbd5da940 100644 --- a/Detectors/Base/test/buildMatBudLUT.C +++ b/Detectors/Base/test/buildMatBudLUT.C @@ -23,13 +23,11 @@ #include #endif -#ifndef GPUCA_ALIGPUCODE // this part is unvisible on GPU version - o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); -bool buildMatBudLUT(int nTst = 30, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomNamePrefix = "o2sim", const std::string& opts = ""); +bool buildMatBudLUT(int nTst = 60, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomName = "o2sim_geometry-aligned.root"); struct LrData { float rMin = 0.f; @@ -249,7 +247,9 @@ void configLayers() // air space between Middle and Outer Barrels zSpanH = 80.f; - lrData.emplace_back(LrData(lrData.back().rMax, 33.5, zSpanH)); + zBin = 10.; + rphiBin = lrData.back().rMax * TMath::Pi() * 2 / 18; + lrData.emplace_back(LrData(lrData.back().rMax, 33.5, zSpanH, zBin, rphiBin)); //=================================================================================== // ITS Outer barrel @@ -259,14 +259,14 @@ void configLayers() zBin = 1.; do { auto rmean = lrData.back().rMax + drStep / 2; - rphiBin = rmean * TMath::Pi() * 2 / (nStave * 10); + rphiBin = rmean * TMath::Pi() * 2 / (nStave * 15); lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 36. - kToler); drStep = 1.; do { auto rmean = lrData.back().rMax + drStep / 2; - rphiBin = rmean * TMath::Pi() * 2 / (nStave * 10); + rphiBin = rmean * TMath::Pi() * 2 / (nStave * 15); lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 38.5 - kToler); @@ -274,14 +274,14 @@ void configLayers() drStep = 0.25; do { auto rmean = lrData.back().rMax + drStep / 2; - rphiBin = rmean * TMath::Pi() * 2 / (nStave * 10); + rphiBin = rmean * TMath::Pi() * 2 / (nStave * 15); lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 41. - kToler); drStep = 1.; do { auto rmean = lrData.back().rMax + drStep / 2; - rphiBin = rmean * TMath::Pi() * 2 / (nStave * 10); + rphiBin = rmean * TMath::Pi() * 2 / (nStave * 15); lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 44. - kToler); @@ -301,21 +301,29 @@ void configLayers() } while (lrData.back().rMax < 55. - kToler); zSpanH = 120.f; - lrData.emplace_back(LrData(lrData.back().rMax, 56.5, zSpanH)); - lrData.emplace_back(LrData(lrData.back().rMax, 60.5, zSpanH)); - lrData.emplace_back(LrData(lrData.back().rMax, 61.5, zSpanH)); + zBin = 10.; + rphiBin = lrData.back().rMax * TMath::Pi() * 2 / 18; + lrData.emplace_back(LrData(lrData.back().rMax, 56.5, zSpanH, zBin, rphiBin)); + + //------------------------------------ + zBin = 1.; + rphiBin = lrData.back().rMax * TMath::Pi() * 2 / 18; + lrData.emplace_back(LrData(lrData.back().rMax, 60.5, zSpanH, zBin, rphiBin)); + rphiBin = lrData.back().rMax * TMath::Pi() * 2 / 18; + lrData.emplace_back(LrData(lrData.back().rMax, 61.5, zSpanH, zBin, rphiBin)); zSpanH = 150.f; - drStep = 3.5; - zBin = 15.; - rphiBin = 10; + drStep = 2; + zBin = 1.; do { + auto rmean = lrData.back().rMax + drStep / 2; + rphiBin = rmean * TMath::Pi() * 2 / (NSect * 2); lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); } while (lrData.back().rMax < 68.5 - kToler); zSpanH = 250.f; - zBin = 25.; - rphiBin = 5; + zBin = 1.; + rphiBin = 2.5; { auto rmean = (lrData.back().rMax + 76) / 2.; rphiBin = rmean * TMath::Pi() * 2 / (NSect * 2); @@ -335,7 +343,7 @@ void configLayers() zBin = 2; { auto rmean = (lrData.back().rMax + 78.5) / 2; - rphiBin = rmean * TMath::Pi() * 2 / (NSect * 12); + rphiBin = rmean * TMath::Pi() * 2 / (NSect * 24); lrData.emplace_back(LrData(lrData.back().rMax, 84.5, zSpanH, zBin, rphiBin)); } @@ -389,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/rescaleLUT.C b/Detectors/Base/test/rescaleLUT.C new file mode 100644 index 0000000000000..9e25c796e43d1 --- /dev/null +++ b/Detectors/Base/test/rescaleLUT.C @@ -0,0 +1,82 @@ +// 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 "DetectorsBase/MatLayerCylSet.h" +#include "Framework/Logger.h" +#include "CCDB/BasicCCDBManager.h" +#include +#endif + +// Macro to extract layers covering selected radial range into the separate LUT file. + +void rescaleLUT(o2::base::MatLayerCylSet* src, const std::string& outName) +{ + struct RescRange { + float rMin, rMax, factor; + }; + std::vector task = { + // put here radial ranges in increasing order with corresponding factors to rescale + {0.1f, 6.f, 1.05}, // e.g. rescale layers covering 0.1 rmax) { + LOGP(error, "rMax={:.2f} of range {} is larger then rMin={:.2f} of range {}, must be in increasing order", rmin, il - 1, rmax, il); + return; + } + o2::base::Ray ray(std::max(src->getRMin(), rmin), 0., 0., std::min(src->getRMax(), rmax), 0., 0.); + if (!src->getLayersRange(ray, lmin, lmax)) { + LOGP(error, "No layers found for {:.2f} < r < {:.2f}", rmin, rmax); + return; + } + if (lmin == lmax) { + LOGP(error, "rMax={:.2f} of range {} and rMin={:.2f} of range {}, correspond to the same slice {} with {:.2f}getLayer(lmin).getRMin(), src->getLayer(lmin).getRMax()); + return; + } + } + + for (size_t il = 0; il < task.size(); il++) { + src->scaleLayersByR(task[il].rMin, task[il].rMax, task[il].factor); + } + if (outName.size()) { + src->writeToFile(outName); + } +} + +void rescaleLUT(const std::string& fname) +{ + auto src = o2::base::MatLayerCylSet::loadFromFile(fname); + if (!src) { + LOGP(error, "failed to open source LUT from {}", fname); + return; + } + auto fnameOut = std::regex_replace(fname, std::regex(R"(.root)"), "_rescaled.root"); + rescaleLUT(src, fnameOut); +} + +void rescaleLUT(long timestamp = -1) +{ + auto& mg = o2::ccdb::BasicCCDBManager::instance(); + mg.setTimestamp(timestamp); + auto src = o2::base::MatLayerCylSet::rectifyPtrFromFile(mg.get("GLO/Param/MatLUT")); + if (!src) { + LOGP(error, "failed to open load LUT from CCDB for timestamp {}", timestamp); + return; + } + auto fnameOut = fmt::format("matbudLUT_ts{}_rescaled.root", timestamp); + rescaleLUT(src, fnameOut); +} 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/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h index e9bd0f7249ef1..4e259c24f44a6 100644 --- a/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h +++ b/Detectors/CPV/reconstruction/include/CPVReconstruction/CTFCoder.h @@ -32,10 +32,10 @@ namespace o2 namespace cpv { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::CPV) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::CPV, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h index 09de778360d74..7192b1b2f6353 100644 --- a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h +++ b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace cpv class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace cpv } // namespace o2 diff --git a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h index 24c229179fe1d..a1851ebb97377 100644 --- a/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h +++ b/Detectors/CPV/workflow/include/CPVWorkflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace cpv class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR = false); + EntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace cpv } // namespace o2 diff --git a/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx b/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx index 7c14dc70dd430..518a646e23cb9 100644 --- a/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/CPV/workflow/src/EntropyDecoderSpec.cxx @@ -25,7 +25,7 @@ namespace o2 namespace cpv { -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -74,7 +74,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"triggers"}, "CPV", "CLUSTERTRIGRECS", 0, Lifetime::Timeframe}, @@ -83,16 +83,17 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_CPV", "CPV", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_CPV", "CPV", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CPV/Calib/CTFDictionaryTree")); + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_CPV", "CPV", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CPV/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "cpv-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } } // namespace cpv diff --git a/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx b/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx index 31ed720e66335..54fb1354ad60c 100644 --- a/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/CPV/workflow/src/EntropyEncoderSpec.cxx @@ -26,7 +26,7 @@ namespace o2 namespace cpv { -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -70,12 +70,14 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("triggers", "CPV", "CLUSTERTRIGRECS", 0, Lifetime::Timeframe); inputs.emplace_back("clusters", "CPV", "CLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "CPV", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CPV/Calib/CTFDictionaryTree")); + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "CPV", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CPV/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -84,9 +86,8 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"CPV", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "CPV", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; diff --git a/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx b/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx index d7e79c4cea430..90fedb1d5eb83 100644 --- a/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/CPV/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::cpv::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::cpv::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } 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 ab03649c0646b..51f2fca2c8303 100644 --- a/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h +++ b/Detectors/CTF/workflow/include/CTFWorkflow/CTFReaderSpec.h @@ -27,11 +27,13 @@ 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{}; std::string fileIRFrames{}; std::string fileRunTimeSpans{}; + std::string dictOpt{}; std::vector ctfIDs{}; bool reverseCTFIDs{false}; bool skipSkimmedOutTF = false; @@ -49,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 3810230637e5f..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()) { @@ -645,7 +684,6 @@ DataProcessorSpec getCTFReaderSpec(const CTFReaderInp& inp) if (!inp.sup0xccdb) { outputs.emplace_back(OutputSpec{{"TFDist"}, o2::header::gDataOriginFLP, o2::header::gDataDescriptionDISTSTF, 0xccdb}); } - options.emplace_back(ConfigParamSpec{"select-ctf-ids", VariantType::String, "", {"comma-separated list CTF IDs to inject (from cumulative counter of CTFs seen)"}}); options.emplace_back(ConfigParamSpec{"reverse-select-ctf-ids", VariantType::Bool, false, {"reverse order of to inject CTF IDs"}}); options.emplace_back(ConfigParamSpec{"impose-run-start-timstamp", VariantType::Int64, 0L, {"impose run start time stamp (ms), ignored if 0"}}); 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 cddf694251a01..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" @@ -52,12 +53,14 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options; options.push_back(ConfigParamSpec{"ctf-input", VariantType::String, "none", {"comma-separated list CTF input files"}}); + options.push_back(ConfigParamSpec{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}); options.push_back(ConfigParamSpec{"onlyDet", VariantType::String, std::string{DetID::ALL}, {"comma-separated list of detectors to accept. Overrides skipDet"}}); options.push_back(ConfigParamSpec{"skipDet", VariantType::String, std::string{DetID::NONE}, {"comma-separate list of detectors to skip"}}); options.push_back(ConfigParamSpec{"loop", VariantType::Int, 0, {"loop N times (infinite for N<0)"}}); 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)"}}); @@ -79,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); } @@ -123,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"); @@ -132,6 +137,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) ctfInput.fileRunTimeSpans = configcontext.options().get("run-time-span-file"); ctfInput.skipSkimmedOutTF = configcontext.options().get("skip-skimmed-out-tf"); ctfInput.invertIRFramesSelection = configcontext.options().get("invert-irframe-selection"); + ctfInput.dictOpt = configcontext.options().get("ctf-dict"); int verbosity = configcontext.options().get("ctf-reader-verbosity"); int rateLimitingIPCID = std::stoi(configcontext.options().get("timeframes-rate-limit-ipcid")); @@ -145,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)); @@ -181,52 +189,54 @@ 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)); + 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)); + 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)); + addSpecs(o2::tpc::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::TRD]) { - addSpecs(o2::trd::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::trd::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::TOF]) { - addSpecs(o2::tof::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::tof::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::FT0]) { - addSpecs(o2::ft0::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::ft0::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::FV0]) { - addSpecs(o2::fv0::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::fv0::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::FDD]) { - addSpecs(o2::fdd::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::fdd::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::MID]) { - addSpecs(o2::mid::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::mid::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::MCH]) { - addSpecs(o2::mch::getEntropyDecoderSpec(verbosity, "mch-entropy-decoder", ctfInput.subspec)); + addSpecs(o2::mch::getEntropyDecoderSpec(verbosity, "mch-entropy-decoder", ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::EMC]) { - addSpecs(o2::emcal::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.decSSpecEMC)); + addSpecs(o2::emcal::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.decSSpecEMC, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::PHS]) { - addSpecs(o2::phos::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::phos::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::CPV]) { - addSpecs(o2::cpv::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::cpv::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::ZDC]) { - addSpecs(o2::zdc::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::zdc::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::HMP]) { - addSpecs(o2::hmpid::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::hmpid::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } if (ctfInput.detMask[DetID::CTP]) { - addSpecs(o2::ctp::getEntropyDecoderSpec(verbosity, ctfInput.subspec)); + addSpecs(o2::ctp::getEntropyDecoderSpec(verbosity, ctfInput.subspec, ctfInput.dictOpt)); } bool combine = configcontext.options().get("combine-devices"); 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/CTP/macro/PlotPbLumi.C b/Detectors/CTP/macro/PlotPbLumi.C index 04666d5bd1cf6..4bda8d25e006e 100644 --- a/Detectors/CTP/macro/PlotPbLumi.C +++ b/Detectors/CTP/macro/PlotPbLumi.C @@ -9,10 +9,10 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file TestCTPScalers.C +/// \file PlotPbLumi.C /// \brief create CTP scalers, test it and add to database /// \author Roman Lietava -// root -b -q "GetScalers.C(\"519499\", 1656286373953)" +// root "PLotPbLumi.C(519499)" #if !defined(__CLING__) || defined(__ROOTCLING__) #include @@ -30,12 +30,21 @@ #include #endif using namespace o2::ctp; -void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-test.cern.ch:8080") +// +// sum = 0: TCE and TSC separatelly otherwise TCE and (TCE+TSC) +// qc = 0: takes scalers from CCDB (available only for finished runs) otherwise from QCCDB (available for active runs) +// t0-tlast: window in seconds counted from beginning of run +// +void PlotPbLumi(int runNumber = 567905, bool sum = 0, bool qc = 0, Double_t t0 = 0., Double_t tlast = 0.) { // - // what = 1: znc rate - // what = 2: (TCE+TSC)/ZNC - // what = 3: TCE/ZNC - std::string mCCDBPathCTPScalers = "CTP/Calib/Scalers"; + // PLots in one canvas + // znc rate/28 + // R = (TCE+TSC)*TVX*B*/ZNC*28 + // R = TCE*TVX*B/ZNC*28 + // R = VCH*TVX*B/ZNC*28 + std::string ccdbHost = "http://alice-ccdb.cern.ch"; + std::string mCCDBPathCTPScalers = "/CTP/Calib/Scalers"; + std::string mCCDBPathCTPScalersQC = "qc/CTP/Scalers"; std::string mCCDBPathCTPConfig = "CTP/Config/Config"; auto& ccdbMgr = o2::ccdb::BasicCCDBManager::instance(); // Timestamp @@ -43,20 +52,26 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te uint64_t timeStamp = (soreor.second - soreor.first) / 2 + soreor.first; std::cout << "Timestamp:" << timeStamp << std::endl; // Filling - std::string sfill = std::to_string(fillN); - std::map metadata; - metadata["fillNumber"] = sfill; - auto lhcifdata = ccdbMgr.getSpecific("GLO/Config/GRPLHCIF", timeStamp, metadata); + auto lhcifdata = ccdbMgr.getForRun("GLO/Config/GRPLHCIF", runNumber); + // auto lhcifdata = ccdbMgr.getSpecific("GLO/Config/GRPLHCIF", timeStamp, metadata); + if (!lhcifdata) { + throw std::runtime_error("No GRPLHCIFData for run " + std::to_string(runNumber)); + } auto bfilling = lhcifdata->getBunchFilling(); std::vector bcs = bfilling.getFilledBCs(); int nbc = bcs.size(); std::cout << "Number of interacting bc:" << nbc << std::endl; // Scalers std::string srun = std::to_string(runNumber); - metadata.clear(); // can be empty + std::map metadata; metadata["runNumber"] = srun; - ccdbMgr.setURL("http://ccdb-test.cern.ch:8080"); - auto scl = ccdbMgr.getSpecific(mCCDBPathCTPScalers, timeStamp, metadata); + CTPRunScalers* scl = nullptr; + if (qc) { + ccdbMgr.setURL("http://ali-qcdb-gpn.cern.ch:8083"); + scl = ccdbMgr.getSpecific(mCCDBPathCTPScalersQC, timeStamp, metadata); + } else { + scl = ccdbMgr.getSpecific(mCCDBPathCTPScalers, timeStamp, metadata); + } if (scl == nullptr) { LOG(info) << "CTPRunScalers not in database, timestamp:" << timeStamp; return; @@ -65,6 +80,7 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te std::vector recs = scl->getScalerRecordO2(); // // CTPConfiguration ctpcfg; + ccdbMgr.setURL("http://alice-ccdb.cern.ch"); auto ctpcfg = ccdbMgr.getSpecific(mCCDBPathCTPConfig, timeStamp, metadata); if (ctpcfg == nullptr) { LOG(info) << "CTPRunConfig not in database, timestamp:" << timeStamp; @@ -85,6 +101,7 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te int tsc = 255; int tce = 255; int vch = 255; + int zncclsi = 255; for (auto const& cls : ctpcls) { if (cls.name.find("CMTVXTSC-B-NOPF") != std::string::npos && tsc == 255) { int itsc = cls.getIndex(); @@ -104,6 +121,12 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te // vch = scl->getScalerIndexForClass(ivch); std::cout << cls.name << ":" << vch << ":" << ivch << std::endl; } + if (cls.name.find("C1ZNC-B-NOPF-CRU") != std::string::npos) { + int iznc = cls.getIndex(); + zncclsi = clsIndexToScaler[iznc]; + // vch = scl->getScalerIndexForClass(ivch); + std::cout << cls.name << ":" << zncclsi << ":" << iznc << std::endl; + } } if (tsc == 255 || tce == 255 || vch == 255) { std::cout << " One of dcalers not available, check config to find alternative)" << std::endl; @@ -120,11 +143,39 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te double_t orbit0 = recs[0].intRecord.orbit; int n = recs.size() - 1; std::cout << " Run duration:" << Trun << " Scalers size:" << n + 1 << std::endl; - Double_t x[n], znc[n], zncpp[n]; - Double_t tcetsctoznc[n], tcetoznc[n], vchtoznc[n]; - for (int i = 0; i < n; i++) { - x[i] = (double_t)(recs[i + 1].intRecord.orbit + recs[i].intRecord.orbit) / 2. - orbit0; - x[i] *= 88e-6; + // + int i0 = 0; + int ilast = 0; + if (t0 != 0. || tlast != 0.) { + for (int i = 0; i < n; i++) { + double_t ttime = recs[i].epochTime - time0; + if (!i0 && t0 < ttime) { + i0 = i; + } + if (!ilast && tlast < ttime) { + ilast = i; + } + } + } else { + ilast = n; + } + n = ilast - i0; + std::cout << "i0:" << i0 << " ilast:" << ilast << std::endl; + // Double_t x[n], znc[n], zncpp[n]; + std::vector xvec(n), zncvec(n), zncppvec(n), zncclassvec(n); + Double_t* x = xvec.data(); + Double_t* znc = zncvec.data(); + Double_t* zncpp = zncppvec.data(); + Double_t* zncclass = zncclassvec.data(); + // Double_t tcetsctoznc[n], tcetoznc[n], vchtoznc[n]; + std::vector tcetsctozncvec(n), tcetozncvec(n), vchtozncvec(n); + Double_t* tcetsctoznc = tcetsctozncvec.data(); + Double_t* tcetoznc = tcetozncvec.data(); + Double_t* vchtoznc = vchtozncvec.data(); + for (int i = i0; i < ilast; i++) { + int iv = i - i0; + x[iv] = (double_t)(recs[i + 1].intRecord.orbit + recs[i].intRecord.orbit) / 2. - orbit0; + x[iv] *= 88e-6; // x[i] = (double_t)(recs[i+1].epochTime + recs[i].epochTime)/2.; double_t tt = (double_t)(recs[i + 1].intRecord.orbit - recs[i].intRecord.orbit); tt = tt * 88e-6; @@ -133,33 +184,53 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te double_t znci = (double_t)(recs[i + 1].scalersInps[25] - recs[i].scalersInps[25]); double_t mu = -TMath::Log(1. - znci / tt / nbc / frev); double_t zncipp = mu * nbc * frev; - zncpp[i] = zncipp / 28.; - znc[i] = znci / 28. / tt; + zncpp[iv] = zncipp / 28.; + znc[iv] = znci / 28. / tt; + // znc class + znci = recs[i + 1].scalers[zncclsi].l1Before - recs[i].scalers[zncclsi].l1Before; + zncclass[iv] = znci / 28. / tt; + // std::cout << znc[i]/zncclass[i] << std::endl; // - auto had = recs[i + 1].scalers[tce].lmBefore - recs[i].scalers[tce].lmBefore; + double_t had = 0; + if (sum) { + had += recs[i + 1].scalers[tce].lmBefore - recs[i].scalers[tce].lmBefore; + } + double_t mutce = -TMath::Log(1. - had / tt / nbc / frev); // std::cout << recs[i+1].scalers[tce].lmBefore << std::endl; had += recs[i + 1].scalers[tsc].lmBefore - recs[i].scalers[tsc].lmBefore; // rat = (double_t)(had)/double_t(recs[i+1].scalersInps[25] - recs[i].scalersInps[25])*28; - tcetsctoznc[i] = (double_t)(had) / zncpp[i] / tt; + tcetsctoznc[iv] = (double_t)(had) / zncpp[iv] / tt; had = recs[i + 1].scalers[tce].lmBefore - recs[i].scalers[tce].lmBefore; // rat = (double_t)(had)/double_t(recs[i+1].scalersInps[25] - recs[i].scalersInps[25])*28; - tcetoznc[i] = (double_t)(had) / zncpp[i] / tt; + tcetoznc[iv] = (double_t)(had) / zncpp[iv] / tt; had = recs[i + 1].scalers[vch].lmBefore - recs[i].scalers[vch].lmBefore; + double_t muvch = -TMath::Log(1. - had / tt / nbc / frev); + // rat = (double_t)(had)/double_t(recs[i+1].scalersInps[25] - recs[i].scalersInps[25])*28; - vchtoznc[i] = (double_t)(had) / zncpp[i] / tt; + vchtoznc[iv] = (double_t)(had) / zncpp[iv] / tt; + // std::cout << "muzdc:" << mu << " mu tce:" << mutce << " muvch:" << muvch << std::endl; } // gStyle->SetMarkerSize(0.5); TGraph* gr1 = new TGraph(n, x, znc); + TGraph* gr11 = new TGraph(n, x, zncpp); // PileuP corrected + TGraph* gr12 = new TGraph(n, x, zncclass); // NOT PileuP corrected TGraph* gr2 = new TGraph(n, x, tcetsctoznc); TGraph* gr3 = new TGraph(n, x, tcetoznc); TGraph* gr4 = new TGraph(n, x, vchtoznc); gr1->SetMarkerStyle(20); + gr11->SetMarkerStyle(20); + gr12->SetMarkerStyle(20); + gr11->SetMarkerColor(kRed); + gr12->SetMarkerColor(kBlue); gr2->SetMarkerStyle(21); gr3->SetMarkerStyle(23); gr4->SetMarkerStyle(23); - gr1->SetTitle("R=ZNC/28 rate [Hz]; time[sec]; R"); - gr2->SetTitle("R=(TSC+TCE)*TVTX*B*28/ZNC; time[sec]; R"); + if (sum) { + gr2->SetTitle("R=(TSC+TCE)*TVTX*B*28/ZNC; time[sec]; R"); + } else { + gr2->SetTitle("R=(TSC)*TVTX*B*28/ZNC; time[sec]; R"); + } // gr2->GetHistogram()->SetMaximum(1.1); // gr2->GetHistogram()->SetMinimum(0.9); gr3->SetTitle("R=(TCE)*TVTX*B*28/ZNC; time[sec]; R"); @@ -168,10 +239,17 @@ void PlotPbLumi(int runNumber, int fillN, std::string ccdbHost = "http://ccdb-te gr4->SetTitle("R=(VCH)*TVTX*B*28/ZNC; time[sec]; R"); // gr4->GetHistogram()->SetMaximum(0.6); // gr4->GetHistogram()->SetMinimum(0.4); + TMultiGraph* mg1 = new TMultiGraph(); + mg1->SetTitle("R=ZNC/28 rate [Hz] (red=PilUp Corrected); time[sec]; R"); + mg1->Add(gr1); + mg1->Add(gr11); + mg1->Add(gr12); TCanvas* c1 = new TCanvas("c1", srun.c_str(), 200, 10, 800, 500); + std::string title = "RUN " + std::to_string(runNumber); + c1->SetTitle(title.c_str()); c1->Divide(2, 2); c1->cd(1); - gr1->Draw("AP"); + mg1->Draw("AP"); c1->cd(2); gr2->Draw("AP"); c1->cd(3); diff --git a/Detectors/CTP/reconstruction/include/CTPReconstruction/CTFCoder.h b/Detectors/CTP/reconstruction/include/CTPReconstruction/CTFCoder.h index 9189df5d12685..8dbc5adadbfc5 100644 --- a/Detectors/CTP/reconstruction/include/CTPReconstruction/CTFCoder.h +++ b/Detectors/CTP/reconstruction/include/CTPReconstruction/CTFCoder.h @@ -34,10 +34,10 @@ namespace o2 namespace ctp { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::CTP) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::CTP, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/CTP/workflow/include/CTPWorkflow/EntropyDecoderSpec.h b/Detectors/CTP/workflow/include/CTPWorkflow/EntropyDecoderSpec.h index eee7abb08d16c..dda45c9f11a34 100644 --- a/Detectors/CTP/workflow/include/CTPWorkflow/EntropyDecoderSpec.h +++ b/Detectors/CTP/workflow/include/CTPWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace ctp class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace ctp } // namespace o2 diff --git a/Detectors/CTP/workflow/include/CTPWorkflow/EntropyEncoderSpec.h b/Detectors/CTP/workflow/include/CTPWorkflow/EntropyEncoderSpec.h index 3a023ce2022dc..a63119264e071 100644 --- a/Detectors/CTP/workflow/include/CTPWorkflow/EntropyEncoderSpec.h +++ b/Detectors/CTP/workflow/include/CTPWorkflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace ctp class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR, bool noLumi); + EntropyEncoderSpec(bool selIR, bool noLumi, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, bool noLumiInput = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, bool noLumiInput = false, const std::string& ctfdictOpt = "none"); } // namespace ctp } // namespace o2 diff --git a/Detectors/CTP/workflow/src/EntropyDecoderSpec.cxx b/Detectors/CTP/workflow/src/EntropyDecoderSpec.cxx index 8c2f5d05aa031..0fa8fb0004e4c 100644 --- a/Detectors/CTP/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/CTP/workflow/src/EntropyDecoderSpec.cxx @@ -24,8 +24,7 @@ namespace o2 { namespace ctp { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -90,7 +89,7 @@ void EntropyDecoderSpec::updateTimeDependentParams(framework::ProcessingContext& } } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"digits"}, "CTP", "DIGITS", 0, Lifetime::Timeframe}, @@ -99,18 +98,19 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_CTP", "CTP", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_CTP", "CTP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CTP/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_CTP", "CTP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CTP/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); inputs.emplace_back("ctpconfig", "CTP", "CTPCONFIG", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/Config", 1)); return DataProcessorSpec{ "ctp-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ignore-ctpinputs-decoding-ctf", VariantType::Bool, false, {"Inputs alignment: false - CTF decoder - has to be compatible with reco: allowed options: 10,01,00"}}, + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ignore-ctpinputs-decoding-ctf", VariantType::Bool, false, {"Inputs alignment: false - CTF decoder - has to be compatible with reco: allowed options: 10,01,00"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace ctp } // namespace o2 diff --git a/Detectors/CTP/workflow/src/EntropyEncoderSpec.cxx b/Detectors/CTP/workflow/src/EntropyEncoderSpec.cxx index 44e64d7505977..902fe22dadcc9 100644 --- a/Detectors/CTP/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/CTP/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace ctp { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, bool nolumi) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR), mNoLumi(nolumi) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, bool nolumi, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR), mNoLumi(nolumi) { mTimer.Stop(); mTimer.Reset(); @@ -77,14 +76,17 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR, bool nolumi) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, bool nolumi, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("digits", "CTP", "DIGITS", 0, Lifetime::Timeframe); if (!nolumi) { inputs.emplace_back("CTPLumi", "CTP", "LUMI", 0, Lifetime::Timeframe); } - inputs.emplace_back("ctfdict", "CTP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CTP/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "CTP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("CTP/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -92,13 +94,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR, bool nolumi) "ctp-entropy-encoder", inputs, Outputs{{"CTP", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "CTP", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR, nolumi)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(selIR, nolumi, 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"}}}}; } - } // namespace ctp } // namespace o2 diff --git a/Detectors/CTP/workflow/src/entropy-encoder-workflow.cxx b/Detectors/CTP/workflow/src/entropy-encoder-workflow.cxx index 1fcaa89be9888..c2b324a4b3bfa 100644 --- a/Detectors/CTP/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/CTP/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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{"no-lumi-input", VariantType::Bool, false, {"Lumi info not available"}}, ConfigParamSpec{"select-ir-frames", VariantType::Bool, false, {"Subscribe and filter according to external IR Frames"}}}; @@ -38,6 +39,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::ctp::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("no-lumi-input"))); + wf.emplace_back(o2::ctp::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("no-lumi-input"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/CTP/workflowScalers/CMakeLists.txt b/Detectors/CTP/workflowScalers/CMakeLists.txt index a31774ac66d69..f02a7f33e2abd 100644 --- a/Detectors/CTP/workflowScalers/CMakeLists.txt +++ b/Detectors/CTP/workflowScalers/CMakeLists.txt @@ -34,3 +34,11 @@ o2_add_executable( SOURCES src/ctp-ccdb-orbit.cxx PUBLIC_LINK_LIBRARIES O2::DataFormatsCTP Boost::program_options) +o2_add_executable( + bk-write + COMPONENT_NAME ctp + SOURCES src/ctp-bk-write.cxx + PUBLIC_LINK_LIBRARIES O2::DataFormatsCTP + O2::CTPWorkflowScalers + AliceO2::BookkeepingApi + Boost::program_options) diff --git a/Detectors/CTP/workflowScalers/include/CTPWorkflowScalers/ctpCCDBManager.h b/Detectors/CTP/workflowScalers/include/CTPWorkflowScalers/ctpCCDBManager.h index 4237ad4501fcc..df2aa79d18697 100644 --- a/Detectors/CTP/workflowScalers/include/CTPWorkflowScalers/ctpCCDBManager.h +++ b/Detectors/CTP/workflowScalers/include/CTPWorkflowScalers/ctpCCDBManager.h @@ -31,8 +31,9 @@ class ctpCCDBManager int saveOrbitReset(long timeStamp); int saveCtpCfg(uint32_t runNumber, long timeStamp); static CTPConfiguration getConfigFromCCDB(long timestamp, std::string run, bool& ok); - static CTPConfiguration getConfigFromCCDB(long timestamp, std::string run); - CTPRunScalers getScalersFromCCDB(long timestamp, std::string, bool& ok); + CTPConfiguration getConfigFromCCDB(long timestamp, std::string run); + CTPRunScalers getScalersFromCCDB(long timestamp, std::string run, bool& ok); + static CTPRunScalers getScalersFromCCDB(long timestamp, std::string, std::string path, bool& ok); static void setCCDBHost(std::string host) { mCCDBHost = host; }; static void setQCDBHost(std::string host) { mQCDBHost = host; }; void setCtpCfgDir(std::string& ctpcfgdir) { mCtpCfgDir = ctpcfgdir; }; diff --git a/Detectors/CTP/workflowScalers/src/ctp-bk-write.cxx b/Detectors/CTP/workflowScalers/src/ctp-bk-write.cxx new file mode 100644 index 0000000000000..8460c07dcc896 --- /dev/null +++ b/Detectors/CTP/workflowScalers/src/ctp-bk-write.cxx @@ -0,0 +1,170 @@ +// 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. + +// 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. + +// example to run: +// +#include +#include +#include +#include +#include "CommonUtils/StringUtils.h" +#include +#include "CTPWorkflowScalers/ctpCCDBManager.h" +#include "BookkeepingApi/BkpClientFactory.h" +#include "BookkeepingApi/BkpClient.h" +#include +#include +#include +#include +namespace bpo = boost::program_options; +// +// Test in the lab +// o2-ctp-bk-write -r 37 -s 1 -c 1 --ccdb='http://acsl-ccdb.cern.ch:8083' -b 'acsl-aliecs.cern.ch:4001' -t 1753185071753 +// +int main(int argc, char** argv) +{ + const std::string testCCDB = "http://ccdb-test.cern.ch:8080"; + // std::string prodCCDB = "http://o2-ccdb.internal"; + const std::string aliceCCDB = "http://alice-ccdb.cern.ch"; + bpo::variables_map vm; + bpo::options_description opt_general("Usage:\n " + std::string(argv[0]) + + " Write ctp config or scalers to BK\n"); + bpo::options_description opt_hidden(""); + bpo::options_description opt_all; + bpo::positional_options_description opt_pos; + try { + auto add_option = opt_general.add_options(); + add_option("help,h", "Print this help message"); + add_option("input-file,f", bpo::value()->default_value("none"), "input file name, none - do not read file"); + add_option("bkhost,b", bpo::value()->default_value("none"), "bk web address"); + add_option("ccdb", bpo::value()->default_value("alice"), "choose databse: test- test ccdb; prod - production ccdb; alice - alice ccdb; else ccdb parameter"); + add_option("run-number,r", bpo::value()->default_value(0), "run number"); + add_option("timestamp,t", bpo::value()->default_value(0), "timestamp; if 0 timestamp is calulated inside this code"); + add_option("cfg,c", bpo::value()->default_value(0), "Do cfg"); + add_option("scalers,s", bpo::value()->default_value(0), "Do scalers"); + // + opt_all.add(opt_general).add(opt_hidden); + bpo::store(bpo::command_line_parser(argc, argv).options(opt_all).positional(opt_pos).run(), vm); + if (vm.count("help")) { + std::cout << opt_general << std::endl; + exit(0); + } + bpo::notify(vm); + } catch (bpo::error& e) { + std::cerr << "ERROR: " << e.what() << std::endl + << std::endl; + std::cerr << opt_general << std::endl; + exit(1); + } catch (std::exception& e) { + std::cerr << e.what() << ", application will now exit" << std::endl; + exit(2); + } + uint64_t timestamp = vm["timestamp"].as(); + // + int ret = 0; + std::vector runs; + int32_t run = vm["run-number"].as(); + std::cout << "run:" << run << std::endl; + if (run) { + std::cout << "pushing" << std::endl; + runs.push_back(std::to_string(run)); + } + // read input file + std::string filename = vm["input-file"].as(); + if (filename != "none") { + std::ifstream file(filename); + if (!file.is_open()) { + LOG(fatal) << "Cannot open file:" << filename << std::endl; + } else { + std::string line; + while (std::getline(file, line)) { + std::cout << line << "\n"; + std::vector tokens = o2::utils::Str::tokenize(line, ' '); + // int run = std::stoi(tokens[0]); + runs.push_back(tokens[0]); + } + } + } + bool cfg = vm["cfg"].as(); + bool scalers = vm["scalers"].as(); + std::cout << "Doing: cfg:" << cfg << " scal:" << scalers << std::endl; + if (cfg || scalers) { + std::string bkhost = vm["bkhost"].as(); + std::unique_ptr mBKClient = o2::bkp::api::BkpClientFactory::create(bkhost); + // get from ccdb + std::string ccdbAddress; + if (vm["ccdb"].as() == "prod") { + // ccdbAddress = prodCCDB; + } else if (vm["ccdb"].as() == "test") { + ccdbAddress = testCCDB; + } else if (vm["ccdb"].as() == "alice") { + ccdbAddress = aliceCCDB; + } else { + ccdbAddress = vm["ccdb"].as(); + } + o2::ctp::ctpCCDBManager::setCCDBHost(ccdbAddress); + std::cout << "CCDB: " << vm["ccdb"].as() << " " << ccdbAddress << std::endl; + std::map metadata; + for (auto const& run : runs) { + metadata["runNumber"] = run; + bool ok; + int runNumber = std::stoi(run); + auto ctpcfg = o2::ctp::ctpCCDBManager::getConfigFromCCDB(timestamp, run, ok); + + if (cfg) { + std::string ctpcfgstr = ctpcfg.getConfigString(); + try { + mBKClient->run()->setRawCtpTriggerConfiguration(runNumber, ctpcfgstr); + } catch (std::runtime_error& error) { + std::cerr << "An error occurred: " << error.what() << std::endl; + // return 1; + } + LOG(info) << "Run BK:" << run << " CFG:" << cfg; + } + if (scalers) { + auto ctpcnts = o2::ctp::ctpCCDBManager::getScalersFromCCDB(timestamp, run, "CTP/Calib/Scalers", ok); + ctpcnts.convertRawToO2(); + std::vector clsinds = ctpcnts.getClassIndexes(); + long ts = ctpcnts.getTimeLimit().second; + int i = 0; + for (auto const& ind : clsinds) { + std::array cntsbk = ctpcnts.getIntegralForClass(i); + std::string clsname = ctpcfg.getClassNameFromHWIndex(cntsbk[0]); + try { + mBKClient->ctpTriggerCounters()->createOrUpdateForRun(runNumber, clsname, ts, cntsbk[1], cntsbk[2], cntsbk[3], cntsbk[4], cntsbk[5], cntsbk[6]); + std::cout << runNumber << " clsname: " << cntsbk[0] << " " << clsname << " t:" << ts << " cnts:" << cntsbk[1] << " " << cntsbk[2] << " " << cntsbk[3] << " " << cntsbk[4] << " " << cntsbk[5] << " " << cntsbk[6] << std::endl; + ; + + } catch (std::runtime_error& error) { + std::cerr << "An error occurred: " << error.what() << std::endl; + // return 1; + } + LOG(debug) << "Run BK scalers ok"; + i++; + } + } + } + // add to bk + } + std::cout << "o2-ctp-bk-write done" << std::endl; + return ret; +} diff --git a/Detectors/CTP/workflowScalers/src/ctpCCDBManager.cxx b/Detectors/CTP/workflowScalers/src/ctpCCDBManager.cxx index df75b21c2effd..74d4a905c93e2 100644 --- a/Detectors/CTP/workflowScalers/src/ctpCCDBManager.cxx +++ b/Detectors/CTP/workflowScalers/src/ctpCCDBManager.cxx @@ -204,10 +204,16 @@ int ctpCCDBManager::saveCtpCfg(uint32_t runNumber, long timeStart) } CTPConfiguration ctpCCDBManager::getConfigFromCCDB(long timestamp, std::string run, bool& ok) { + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); mgr.setURL(mCCDBHost); std::map metadata; // can be empty metadata["runNumber"] = run; + if (timestamp == 0) { + // Timestamp + auto soreor = mgr.getRunDuration(std::stoi(run)); + timestamp = (soreor.second - soreor.first) / 2 + soreor.first; + } auto ctpconfigdb = mgr.getSpecific(CCDBPathCTPConfig, timestamp, metadata); if (ctpconfigdb == nullptr) { LOG(info) << "CTP config not in database, timestamp:" << timestamp; @@ -245,3 +251,24 @@ CTPRunScalers ctpCCDBManager::getScalersFromCCDB(long timestamp, std::string run } return *ctpscalers; } +CTPRunScalers ctpCCDBManager::getScalersFromCCDB(long timestamp, std::string run, std::string path, bool& ok) +{ + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setURL(mCCDBHost); + std::map metadata; // can be empty + metadata["runNumber"] = run; + if (timestamp == 0) { + // Timestamp + auto soreor = mgr.getRunDuration(std::stoi(run)); + timestamp = (soreor.second - soreor.first) / 2 + soreor.first; + } + auto ctpscalers = mgr.getSpecific(path, timestamp, metadata); + if (ctpscalers == nullptr) { + LOG(info) << "CTPRunScalers not in database, timestamp:" << timestamp; + ok = 0; + } else { + // ctpscalers->printStream(std::cout); + ok = 1; + } + return *ctpscalers; +} \ No newline at end of file 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/Calibration/testMacros/getRunParameters.cxx b/Detectors/Calibration/testMacros/getRunParameters.cxx index c06926511c95d..d3f9b0a2ece69 100644 --- a/Detectors/Calibration/testMacros/getRunParameters.cxx +++ b/Detectors/Calibration/testMacros/getRunParameters.cxx @@ -53,6 +53,18 @@ void writeDurationToFile(long duration) fclose(fptr); } +void writeSORToFile(long sor) +{ + + FILE* fptr = fopen("SOR.txt", "w"); + if (fptr == nullptr) { + LOGP(fatal, "ERROR: Could not open file to write SOR!"); + return; + } + fprintf(fptr, "%ld", sor); + fclose(fptr); +} + void writeBFieldToFile(float b) { @@ -165,6 +177,7 @@ int main(int argc, char* argv[]) ir = -1.f; writeIRtoFile(ir); writeDurationToFile(run_O2duration); + writeSORToFile(tsSOR); return 0; } } @@ -204,6 +217,7 @@ int main(int argc, char* argv[]) } writeIRtoFile(ir); writeDurationToFile(duration); + writeSORToFile(tsSOR); return 0; } 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/src/DataPointCreator.cxx b/Detectors/DCS/src/DataPointCreator.cxx index 06b0321ad2c94..0bfb5bcd7d387 100644 --- a/Detectors/DCS/src/DataPointCreator.cxx +++ b/Detectors/DCS/src/DataPointCreator.cxx @@ -37,10 +37,9 @@ DataPointCompositeObject createDataPointCompositeObject(const std::string& alias template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, float val, uint32_t seconds, uint16_t msec, uint16_t flags) { - float tmp[2]; - tmp[0] = val; - tmp[1] = 0; - return createDPCOM(alias, reinterpret_cast(&tmp[0]), seconds, msec, flags, DeliveryType::DPVAL_FLOAT); + uint64_t tmp = 0; + memcpy(&tmp, &val, sizeof(val)); + return createDPCOM(alias, &tmp, seconds, msec, flags, DeliveryType::DPVAL_FLOAT); } template <> @@ -54,36 +53,38 @@ template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, uint32_t val, uint32_t seconds, uint16_t msec, uint16_t flags) { uint64_t tmp{val}; - return createDPCOM(alias, reinterpret_cast(&tmp), seconds, msec, flags, DeliveryType::DPVAL_UINT); + return createDPCOM(alias, &tmp, seconds, msec, flags, DeliveryType::DPVAL_UINT); } template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, long long val, uint32_t seconds, uint16_t msec, uint16_t flags) { uint64_t tmp{static_cast(val)}; - return createDPCOM(alias, reinterpret_cast(&tmp), seconds, msec, flags, DeliveryType::DPVAL_UINT); + return createDPCOM(alias, &tmp, seconds, msec, flags, DeliveryType::DPVAL_UINT); } template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, char val, uint32_t seconds, uint16_t msec, uint16_t flags) { - return createDPCOM(alias, reinterpret_cast(&val), seconds, msec, flags, DeliveryType::DPVAL_CHAR); + uint64_t tmp = 0; + memcpy(&tmp, &val, 1); + return createDPCOM(alias, &tmp, seconds, msec, flags, DeliveryType::DPVAL_CHAR); } template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, bool val, uint32_t seconds, uint16_t msec, uint16_t flags) { uint64_t tmp{val}; - return createDPCOM(alias, reinterpret_cast(&tmp), seconds, msec, flags, DeliveryType::DPVAL_BOOL); + return createDPCOM(alias, &tmp, seconds, msec, flags, DeliveryType::DPVAL_BOOL); } template <> DataPointCompositeObject createDataPointCompositeObject(const std::string& alias, std::string val, uint32_t seconds, uint16_t msec, uint16_t flags) { constexpr int N{56}; - char str[N]; - strncpy(str, val.c_str(), N); - return createDPCOM(alias, reinterpret_cast(&str[0]), seconds, msec, flags, DeliveryType::DPVAL_STRING); + uint64_t tmp[N / sizeof(uint64_t)]; + strncpy((char*)tmp, val.c_str(), N); + return createDPCOM(alias, tmp, seconds, msec, flags, DeliveryType::DPVAL_STRING); } } // namespace o2::dcs 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/base/include/EMCALBase/ClusterFactory.h b/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h index a7e81d38838a3..0c3438042ca77 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h +++ b/Detectors/EMCAL/base/include/EMCALBase/ClusterFactory.h @@ -401,6 +401,10 @@ class ClusterFactory /// in cell units void evalElipsAxis(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; + /// + /// Calculate the number of local maxima in the cluster + void evalNExMax(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; + /// /// Time is set to the time of the digit with the maximum energy void evalTime(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const; diff --git a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h index b4621d4b6e434..d07f42689bf7a 100644 --- a/Detectors/EMCAL/base/include/EMCALBase/Geometry.h +++ b/Detectors/EMCAL/base/include/EMCALBase/Geometry.h @@ -429,6 +429,14 @@ class Geometry /// \return Position (0 - phi, 1 - eta) of the cell inside teh supermodule std::tuple GetCellPhiEtaIndexInSModule(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; + /// \brief Get topological row and column of cell in SM (same as for clusteriser with artifical gaps) + /// \param supermoduleID super module number + /// \param moduleID module number + /// \param phiInModule index in phi direction in module + /// \param etaInModule index in phi direction in module + /// \return tuple with (row, column) of the cell, which is global numbering scheme + std::tuple GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const; + /// \brief Adapt cell indices in supermodule to online indexing /// \param supermoduleID super module number of the channel/cell /// \param iphi row/phi cell index, modified for DCal diff --git a/Detectors/EMCAL/base/src/ClusterFactory.cxx b/Detectors/EMCAL/base/src/ClusterFactory.cxx index 342f54fd94591..1752e5c0e98ee 100644 --- a/Detectors/EMCAL/base/src/ClusterFactory.cxx +++ b/Detectors/EMCAL/base/src/ClusterFactory.cxx @@ -120,6 +120,9 @@ o2::emcal::AnalysisCluster ClusterFactory::buildCluster(int clusterIn evalElipsAxis(inputsIndices, clusterAnalysis); evalDispersion(inputsIndices, clusterAnalysis); + // evaluate number of local maxima + evalNExMax(inputsIndices, clusterAnalysis); + evalCoreEnergy(inputsIndices, clusterAnalysis); evalTime(inputsIndices, clusterAnalysis); @@ -489,6 +492,64 @@ void ClusterFactory::evalCoreEnergy(gsl::span inputsIndice clusterAnalysis.setCoreEnergy(coreEnergy); } +/// +/// Calculate the number of local maxima in the cluster +//____________________________________________________________________________ +template +void ClusterFactory::evalNExMax(gsl::span inputsIndices, AnalysisCluster& clusterAnalysis) const +{ + // Pre-compute cell indices and energies for all cells in cluster to avoid multiple expensive geometry lookups + const size_t n = inputsIndices.size(); + std::vector rows; + std::vector columns; + std::vector energies; + + rows.reserve(n); + columns.reserve(n); + energies.reserve(n); + + for (auto iInput : inputsIndices) { + auto [nSupMod, nModule, nIphi, nIeta] = mGeomPtr->GetCellIndex(mInputsContainer[iInput].getTower()); + + // get a nice topological indexing that is done in exactly the same way as used by the clusterizer + // this way we can handle the shared cluster cases correctly + const auto [row, column] = mGeomPtr->GetTopologicalRowColumn(nSupMod, nModule, nIphi, nIeta); + + rows.push_back(row); + columns.push_back(column); + energies.push_back(mInputsContainer[iInput].getEnergy()); + } + + // Now find local maxima using pre-computed data + int nExMax = 0; + for (size_t i = 0; i < n; i++) { + // this cell is assumed to be local maximum unless we find a higher energy cell in the neighborhood + bool isExMax = true; + + // loop over all other cells in cluster + for (size_t j = 0; j < n; j++) { + if (i == j) { + continue; + } + + // adjacent cell is any cell with adjacent phi or eta index + if (std::abs(rows[i] - rows[j]) <= 1 && + std::abs(columns[i] - columns[j]) <= 1) { + + // if there is a cell with higher energy than the current cell, it is not a local maximum + if (energies[j] > energies[i]) { + isExMax = false; + break; + } + } + } + if (isExMax) { + nExMax++; + } + } + clusterAnalysis.setNExMax(nExMax); +} + /// /// Calculates the axis of the shower ellipsoid in eta and phi /// in cell units diff --git a/Detectors/EMCAL/base/src/Geometry.cxx b/Detectors/EMCAL/base/src/Geometry.cxx index c194f570e47d1..3707e22f2da57 100644 --- a/Detectors/EMCAL/base/src/Geometry.cxx +++ b/Detectors/EMCAL/base/src/Geometry.cxx @@ -1103,6 +1103,30 @@ std::tuple Geometry::GetCellPhiEtaIndexInSModule(int supermoduleID, in return std::make_tuple(phiInSupermodule, etaInSupermodule); } +std::tuple Geometry::GetTopologicalRowColumn(int supermoduleID, int moduleID, int phiInModule, int etaInModule) const +{ + auto [iphi, ieta] = GetCellPhiEtaIndexInSModule(supermoduleID, moduleID, phiInModule, etaInModule); + int row = iphi; + int column = ieta; + + // Add shifts wrt. supermodule and type of calorimeter + // NOTE: + // * Rows (phi) are arranged that one space is left empty between supermodules in phi + // This is due to the physical gap that forbids clustering + // * For DCAL, there is an additional empty column between two supermodules in eta + // Again, this is to account for the gap in DCAL + + row += supermoduleID / 2 * (24 + 1); + // In DCAL, leave a gap between two SMs with same phi + if (!IsDCALSM(supermoduleID)) { // EMCAL + column += supermoduleID % 2 * 48; + } else { + column += supermoduleID % 2 * (48 + 1); + } + + return std::make_tuple(static_cast(row), static_cast(column)); +} + std::tuple Geometry::ShiftOnlineToOfflineCellIndexes(Int_t supermoduleID, Int_t iphi, Int_t ieta) const { if (supermoduleID == 13 || supermoduleID == 15 || supermoduleID == 17) { diff --git a/Detectors/EMCAL/calib/include/EMCALCalib/CellRecalibrator.h b/Detectors/EMCAL/calib/include/EMCALCalib/CellRecalibrator.h index 571b43d05ef08..ea8a0445bbe5e 100644 --- a/Detectors/EMCAL/calib/include/EMCALCalib/CellRecalibrator.h +++ b/Detectors/EMCAL/calib/include/EMCALCalib/CellRecalibrator.h @@ -62,7 +62,7 @@ class CellRecalibrator public: /// \class CellTypeException /// \brief Handling of invalid cell types in calibration - class CellTypeException : public std::exception + class CellTypeException final : public std::exception { public: /// \brief Constructor @@ -73,7 +73,7 @@ class CellRecalibrator /// \brief Get error message of the exception /// \return Error message - const char* what() const noexcept final + [[nodiscard]] char const* what() const noexcept final { return "Only possible to calibrate cells of type high gain or low gain"; } @@ -208,4 +208,4 @@ std::ostream& operator<<(std::ostream& in, const CellRecalibrator& calib); } // namespace o2 -#endif // !ALCEO2_EMCAL_CELLRECALIBRATOR_H \ No newline at end of file +#endif // !ALCEO2_EMCAL_CELLRECALIBRATOR_H diff --git a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/CTFCoder.h b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/CTFCoder.h index 1617a9f1a7d54..706b9bf8138a6 100644 --- a/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/CTFCoder.h +++ b/Detectors/EMCAL/reconstruction/include/EMCALReconstruction/CTFCoder.h @@ -32,10 +32,10 @@ namespace o2 namespace emcal { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::EMC) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::EMC, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF 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/EMCAL/reconstruction/run/rawReaderTRUDigits.cxx b/Detectors/EMCAL/reconstruction/run/rawReaderTRUDigits.cxx deleted file mode 100644 index 6fc119dc69521..0000000000000 --- a/Detectors/EMCAL/reconstruction/run/rawReaderTRUDigits.cxx +++ /dev/null @@ -1,171 +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 rawReaderFileNew.cxx -/// \author Markus Fasel , Oak Ridge National Laboratory - -#include -#include - -#include - -#include "DetectorsRaw/RawFileReader.h" -#include "DetectorsRaw/RDHUtils.h" -#include "EMCALBase/Mapper.h" -#include "EMCALBase/TriggerMappingV2.h" -#include "EMCALReconstruction/AltroDecoder.h" -#include "EMCALReconstruction/RawReaderMemory.h" -#include - -namespace bpo = boost::program_options; -// using namespace o2::emcal; - -int main(int argc, char** argv) -{ - bpo::variables_map vm; - bpo::options_description opt_general("Usage:\n " + std::string(argv[0]) + - " \n" - " Tool will decode the DDLx data for EMCAL 0\n" - "Commands / Options"); - bpo::options_description opt_hidden(""); - bpo::options_description opt_all; - bpo::positional_options_description opt_pos; - - try { - auto add_option = opt_general.add_options(); - add_option("help,h", "Print this help message"); - add_option("verbose,v", bpo::value()->default_value(0), "Select verbosity level [0 = no output]"); - add_option("version", "Print version information"); - add_option("input-file,i", bpo::value()->required(), "Specifies input file."); - add_option("debug,d", bpo::value()->default_value(0), "Select debug output level [0 = no debug output]"); - - opt_all.add(opt_general).add(opt_hidden); - bpo::store(bpo::command_line_parser(argc, argv).options(opt_all).positional(opt_pos).run(), vm); - - if (vm.count("help") || argc == 1) { - std::cout << opt_general << std::endl; - exit(0); - } - - if (vm.count("version")) { - // std::cout << GitInfo(); - exit(0); - } - - bpo::notify(vm); - } catch (bpo::error& e) { - std::cerr << "ERROR: " << e.what() << std::endl - << std::endl; - std::cerr << opt_general << std::endl; - exit(1); - } catch (std::exception& e) { - std::cerr << e.what() << ", application will now exit" << std::endl; - exit(2); - } - - auto rawfilename = vm["input-file"].as(); - - o2::raw::RawFileReader reader; - reader.setDefaultDataOrigin(o2::header::gDataOriginEMC); - reader.setDefaultDataDescription(o2::header::gDataDescriptionRawData); - reader.setDefaultReadoutCardType(o2::raw::RawFileReader::RORC); - reader.addFile(rawfilename); - reader.init(); - - o2::emcal::MappingHandler mapper; - o2::emcal::TriggerMappingV2 triggermapping; - - std::unique_ptr treefile(TFile::Open("trudata.root", "RECREATE")); - TTree trudata("trudata", "Tree with TRU data"); - // branches in tree - struct collisiontrigger { - unsigned long bc; - unsigned long orbit; - } mycollision; - int absFastOR; - int starttime; - std::vector timesamples; - tree->Branch(&mycollision, "collisiontrigger", "bc,orbit/l"); - tree->Branch(&starttime, "starttime", "starttime/i"); - tree->Branch(×amples, "timesamples", ""); // @todo check how to write std::vector to tree; - - while (1) { - int tfID = reader.getNextTFToRead(); - if (tfID >= reader.getNTimeFrames()) { - LOG(info) << "nothing left to read after " << tfID << " TFs read"; - break; - } - std::vector dataBuffer; // where to put extracted data - for (int il = 0; il < reader.getNLinks(); il++) { - auto& link = reader.getLink(il); - std::cout << "Decoding link " << il << std::endl; - - auto sz = link.getNextTFSize(); // size in bytes needed for the next TF of this link - dataBuffer.resize(sz); - link.readNextTF(dataBuffer.data()); - - // Parse - o2::emcal::RawReaderMemory parser(dataBuffer); - while (parser.hasNext()) { - parser.next(); - auto rdh = parser.getRawHeader(); - auto ddl = o2::raw::RDHUtils::getFEEID(parser.getRawHeader()); - // Exclude STU DDLs - if (ddl >= 40) { - continue; - } - - mycollision.bc = o2::raw::RDHUtils::getTriggerBC(rdh); - mycollision.orbit = o2::raw::RDHUtils::getTriggerOrbit(rdh); - - o2::emcal::AltroDecoder decoder(parser); - decoder.decode(); - auto& ddlmapping = mapper.getMappingForDDL(ddl); - - std::cout << decoder.getRCUTrailer() << std::endl; - for (auto& chan : decoder.getChannels()) { - if (ddlmapping.getChannelType(chan.getHardwareAddress) != o2::emcal::ChannelType_t::TRU) { - continue; - } - std::cout << "Hw address: " << chan.getHardwareAddress() << std::endl; - // Get absolute FastOR index - this will tell us where on the EMCAL surface the FastOR is - // TRU index is encoded in column, needs to be converted to an absoluted FastOR ID via the - // trigger mapping. The absoluted FastOR ID can be connected via the geometry to tower IDs - // from the FEC data. - // we are only interested in the FastORs for now, skip patches starting from 96 - auto fastorInTRU = ddlmapping.getColumn(chan.getHardwareAddress()); - if (fastorInTRU >= 96) { - // indices starting from 96 encode patches, not FastORs - continue; - } - auto truindex = triggermapping.getTRUIndexFromOnlineHardareAddree(chan.getHardwareAddress(), ddl, ddl / 2); - auto absFastOrID = triggermapping.getAbsFastORIndexFromIndexInTRU(truindex, fastorInTRU); - - for (auto& bunch : chan.getBunches()) { - std::cout << "BunchLength: " << int(bunch.getBunchLength()) << std::endl; - auto adcs = bunch.getADC(); - int time = bunch.getStartTime(); - starttime = time; - timesamples.clear(); - timesamples.resize(adcs.size()); - std::copy(adcs.begin(), adcs.end(), timesamples.begin()); - trudata.Fill(); - for (int i = adcs.size() - 1; i >= 0; i--) { - std::cout << "Timebin " << time << ", ADC " << adcs[i] << std::endl; - time--; - } - } - } - } - } - reader.setNextTFToRead(++tfID); - } -} \ No newline at end of file diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyDecoderSpec.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyDecoderSpec.h index 0215e0ae65e43..9cc5ba7887473 100644 --- a/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyDecoderSpec.h +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace emcal class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity, unsigned int sspecOut); + EntropyDecoderSpec(int verbosity, unsigned int sspecOut, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspecInp, unsigned int sspecOut = 0); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspecInp, unsigned int sspecOut = 0, const std::string& ctfdictOpt = "none"); } // namespace emcal } // namespace o2 diff --git a/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyEncoderSpec.h b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyEncoderSpec.h index cdfb342e7ff11..df502beef30df 100644 --- a/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyEncoderSpec.h +++ b/Detectors/EMCAL/workflow/include/EMCALWorkflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace emcal class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace emcal } // namespace o2 diff --git a/Detectors/EMCAL/workflow/src/EntropyDecoderSpec.cxx b/Detectors/EMCAL/workflow/src/EntropyDecoderSpec.cxx index 700f468e9e73d..ecc0e45492bea 100644 --- a/Detectors/EMCAL/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/EMCAL/workflow/src/EntropyDecoderSpec.cxx @@ -24,8 +24,7 @@ namespace o2 { namespace emcal { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, unsigned int sspecOut) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder), mSSpecOut(sspecOut) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, unsigned int sspecOut, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt), mSSpecOut(sspecOut) { mTimer.Stop(); mTimer.Reset(); @@ -74,7 +73,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspecInp, unsigned int sspecOut) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspecInp, unsigned int sspecOut, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"triggers"}, "EMC", "CELLSTRGR", sspecOut, Lifetime::Timeframe}, @@ -83,17 +82,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspecInp, un std::vector inputs; inputs.emplace_back("ctf_EMC", "EMC", "CTFDATA", sspecInp, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_EMC", "EMC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("EMC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_EMC", "EMC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("EMC/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "emcal-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity, sspecOut)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, sspecOut, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace emcal } // namespace o2 diff --git a/Detectors/EMCAL/workflow/src/EntropyEncoderSpec.cxx b/Detectors/EMCAL/workflow/src/EntropyEncoderSpec.cxx index 773c4c65fc9fe..2928a71a167bc 100644 --- a/Detectors/EMCAL/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/EMCAL/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace emcal { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -71,12 +70,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("triggers", "EMC", "CELLSTRGR", 0, Lifetime::Timeframe); inputs.emplace_back("cells", "EMC", "CELLS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "EMC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("EMC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "EMC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("EMC/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -85,14 +87,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"EMC", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "EMC", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{ - {"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"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"}}}}; + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace emcal } // namespace o2 diff --git a/Detectors/EMCAL/workflow/src/StandaloneAODProducerSpec.cxx b/Detectors/EMCAL/workflow/src/StandaloneAODProducerSpec.cxx index 73987ce6d1c1b..227fc373bf20c 100644 --- a/Detectors/EMCAL/workflow/src/StandaloneAODProducerSpec.cxx +++ b/Detectors/EMCAL/workflow/src/StandaloneAODProducerSpec.cxx @@ -17,7 +17,6 @@ #include "Framework/InputRecordWalker.h" #include "Framework/Logger.h" #include "Framework/TableBuilder.h" -#include "Framework/TableTreeHelpers.h" #include "MathUtils/Utils.h" using namespace o2::framework; @@ -105,7 +104,7 @@ void StandaloneAODProducerSpec::run(ProcessingContext& pc) o2::math_utils::detail::truncateFloatFraction(cell.getTimeStamp(), mCaloTime), cell.getType(), 1); // hard coded for emcal (-1 would be undefined, 0 phos) - } // end of cell loop + } // end of cell loop // filled only with BCID, rest dummy for no2 caloCellsTRGTableCursor(0, diff --git a/Detectors/EMCAL/workflow/src/entropy-encoder-workflow.cxx b/Detectors/EMCAL/workflow/src/entropy-encoder-workflow.cxx index e6af02fa10d49..d5264aabc0566 100644 --- a/Detectors/EMCAL/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/EMCAL/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::emcal::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::emcal::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/FIT/FDD/base/src/Geometry.cxx b/Detectors/FIT/FDD/base/src/Geometry.cxx index 441a30fdda44f..149c086f4fc81 100644 --- a/Detectors/FIT/FDD/base/src/Geometry.cxx +++ b/Detectors/FIT/FDD/base/src/Geometry.cxx @@ -152,7 +152,7 @@ void Geometry::buildGeometry() if (!vCaveRB24) { LOG(fatal) << "Could not find the top volume for A-side"; } - const Float_t kPosFDA = 1696.67 - 1313.347; // z-center of assembly (cm) + const Float_t kPosFDA = 1696.67 - 1313.347 - 75.; // z-center of assembly (cm) vCaveRB24->AddNode(vFDAarray, 1, new TGeoTranslation(0., 0., kPosFDA - kFDACelldz / 2. - 0.1)); vCaveRB24->AddNode(vFDAarray, 2, new TGeoTranslation(0., 0., kPosFDA + kFDACelldz / 2. + 0.1)); diff --git a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h index dc11174908c75..24649f73a4ca3 100644 --- a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h +++ b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/CTFCoder.h @@ -33,10 +33,10 @@ namespace o2 namespace fdd { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FDD) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FDD, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode digits to buffer with CTF @@ -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/reconstruction/include/FDDReconstruction/ReadRaw.h b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/ReadRaw.h deleted file mode 100644 index 54c8b7b203edb..0000000000000 --- a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/ReadRaw.h +++ /dev/null @@ -1,68 +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 ReadRaw.h -/// \brief Reads raw data and converts to digits -/// \author Maciej.Slupecki@cern.ch, arvind.khuntia@cern.ch, based on the FT0 code -// RAW data format description: DataFormat/Detectors/FIT/FDD/RawEventData - -#ifndef ALICEO2_FDD_READRAW_H_ -#define ALICEO2_FDD_READRAW_H_ - -#include -#include -#include -#include -#include -#include -#include -#include "TBranch.h" -#include "TTree.h" -#include "CommonDataFormat/InteractionRecord.h" -#include "DataFormatsFDD/Digit.h" -#include "DataFormatsFDD/ChannelData.h" -#include "DataFormatsFDD/LookUpTable.h" -#include "DataFormatsFDD/RawEventData.h" - -namespace o2 -{ -namespace fdd -{ -class ReadRaw -{ - public: - ReadRaw() = default; - ReadRaw(bool doConversionToDigits, const std::string inputRawFilePath = "fdd.raw", const std::string outputRawFilePath = "fdddigitsFromRaw.root"); - void readRawData(const LookUpTable& lut); - void writeDigits(const std::string& outputDigitsFilePath); - void close(); - - private: - std::ifstream mRawFileIn; - std::map> mDigitAccum; // digit accumulator - - template - TBranch* getOrMakeBranch(TTree& tree, std::string brname, T* ptr) - { - if (auto br = tree.GetBranch(brname.c_str())) { - br->SetAddress(static_cast(ptr)); - return br; - } - // otherwise make it - return tree.Branch(brname.c_str(), ptr); - } - - ClassDefNV(ReadRaw, 1); -}; - -} // namespace fdd -} // namespace o2 -#endif diff --git a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/Reconstructor.h b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/Reconstructor.h index 161b800a2c3ca..8881605b652ac 100644 --- a/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/Reconstructor.h +++ b/Detectors/FIT/FDD/reconstruction/include/FDDReconstruction/Reconstructor.h @@ -17,6 +17,7 @@ #include #include "DataFormatsFDD/Digit.h" #include "DataFormatsFDD/RecPoint.h" +#include "DataFormatsFIT/DeadChannelMap.h" namespace o2 { namespace fdd @@ -30,10 +31,16 @@ class Reconstructor gsl::span inChData, std::vector& RecPoints, std::vector& outChData); - void finish(); + void setDeadChannelMap(o2::fit::DeadChannelMap const* deadChannelMap) + { + LOG(info) << "Updated dead channel map"; + mDeadChannelMap = deadChannelMap; + } + private: + o2::fit::DeadChannelMap const* mDeadChannelMap = nullptr; ClassDefNV(Reconstructor, 3); }; } // namespace fdd diff --git a/Detectors/FIT/FDD/reconstruction/src/Reconstructor.cxx b/Detectors/FIT/FDD/reconstruction/src/Reconstructor.cxx index 3a87a11046a77..7d133e30df08e 100644 --- a/Detectors/FIT/FDD/reconstruction/src/Reconstructor.cxx +++ b/Detectors/FIT/FDD/reconstruction/src/Reconstructor.cxx @@ -33,7 +33,12 @@ void Reconstructor::process(o2::fdd::Digit const& digitBC, gsl::spanisChannelAlive(inChData[ich].mPMNumber)) { + LOG(debug) << "Channel " << ich << " is dead - discarding data"; + continue; + } bool inTime = inChData[ich].getFlag(ChannelData::EEventDataBit::kIsEventInTVDC); bool inAdcGate = inChData[ich].getFlag(ChannelData::EEventDataBit::kIsCFDinADCgate); if (inAdcGate) { diff --git a/Detectors/FIT/FDD/simulation/include/FDDSimulation/Digits2Raw.h b/Detectors/FIT/FDD/simulation/include/FDDSimulation/Digits2Raw.h deleted file mode 100644 index 4afcf5da37ae8..0000000000000 --- a/Detectors/FIT/FDD/simulation/include/FDDSimulation/Digits2Raw.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. - -/// \file Digits2Raw.h -/// \brief converts digits to raw format -/// \author Maciej.Slupecki@cern.ch -// based on FV0 - -#ifndef ALICEO2_FDD_DIGITS2RAW_H_ -#define ALICEO2_FDD_DIGITS2RAW_H_ - -#include "Headers/RAWDataHeader.h" -#include "CommonDataFormat/InteractionRecord.h" -#include "DataFormatsFDD/RawEventData.h" -#include "DataFormatsFDD/LookUpTable.h" -#include "DataFormatsFDD/ChannelData.h" -#include "DataFormatsFDD/Digit.h" -#include "DetectorsRaw/HBFUtils.h" -#include "DetectorsRaw/RawFileWriter.h" -#include -#include -#include -#include -#include -#include - -namespace o2 -{ -namespace fdd -{ -class Digits2Raw -{ - public: - Digits2Raw() = default; - void readDigits(const std::string& outDir, const std::string& fileDigitsName); - void convertDigits(o2::fdd::Digit bcdigits, - gsl::span pmchannels, - const o2::fdd::LookUpTable& lut); - - o2::raw::RawFileWriter& getWriter() { return mWriter; } - void setFilePerLink(bool v) { mOutputPerLink = v; } - bool getFilePerLink() const { return mOutputPerLink; } - - int carryOverMethod(const header::RDHAny* rdh, const gsl::span data, - const char* ptr, int maxSize, int splitID, - std::vector& trailer, std::vector& header) const; - - private: - static constexpr uint32_t sTcmLink = 2; - static constexpr uint16_t sCruId = 0; - static constexpr uint32_t sEndPointId = sCruId; - - void makeGBTHeader(EventHeader& eventHeader, int link, o2::InteractionRecord const& mIntRecord); - void fillSecondHalfWordAndAddData(int iChannelPerLink, int prevPmLink, const o2::InteractionRecord& ir); - RawEventData mRawEventData; - o2::fdd::Triggers mTriggers; - o2::raw::RawFileWriter mWriter{"FDD"}; - bool mOutputPerLink = false; - ///////////////////////////////////////////////// - - ClassDefNV(Digits2Raw, 1); -}; - -} // namespace fdd -} // namespace o2 -#endif diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyDecoderSpec.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyDecoderSpec.h index a6ee132ee0c34..1fd3cd7835cd9 100644 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyDecoderSpec.h +++ b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace fdd class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace fdd } // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyEncoderSpec.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyEncoderSpec.h index 87dcca02e869f..37d43f477e836 100644 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyEncoderSpec.h +++ b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace fdd class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace fdd } // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataProcessSpec.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataProcessSpec.h deleted file mode 100644 index 6ed465b6181dd..0000000000000 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataProcessSpec.h +++ /dev/null @@ -1,61 +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 RawDataProcessSpec.h - -#ifndef O2_FDD_RAWDATAPROCESSSPEC_H -#define O2_FDD_RAWDATAPROCESSSPEC_H - -#include "Framework/CallbackService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/Lifetime.h" -#include "Framework/Output.h" -#include "Framework/WorkflowSpec.h" -#include "Framework/SerializationMethods.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" - -#include "FDDRaw/DigitBlockFDD.h" -#include "DataFormatsFDD/Digit.h" -#include "DataFormatsFDD/ChannelData.h" - -#include -#include -#include - -using namespace o2::framework; - -namespace o2 -{ -namespace fdd -{ - -class RawDataProcessSpec : public Task -{ - public: - RawDataProcessSpec(bool dumpEventBlocks) : mDumpEventBlocks(dumpEventBlocks) {} - ~RawDataProcessSpec() override = default; - void init(InitContext& ic) final; - void run(ProcessingContext& pc) final; - - private: - bool mDumpEventBlocks; - - o2::header::DataOrigin mOrigin = o2::header::gDataOriginFDD; -}; - -framework::DataProcessorSpec getFDDRawDataProcessSpec(bool dumpProcessor); - -} // namespace fdd -} // namespace o2 - -#endif /* O2_FDDDATAPROCESSDPL_H */ diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataReaderSpec.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataReaderSpec.h deleted file mode 100644 index c3b0349826e98..0000000000000 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawDataReaderSpec.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. - -/// @file RawDataReaderSpec.h - -#ifndef O2_FDD_RAWDATAREADERSPEC_H -#define O2_FDD_RAWDATAREADERSPEC_H - -#include "DataFormatsFDD/LookUpTable.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" -#include "Framework/CallbackService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/Lifetime.h" -#include "Framework/Output.h" -#include "Framework/WorkflowSpec.h" -#include "Framework/SerializationMethods.h" -#include "DPLUtils/DPLRawParser.h" -#include "DetectorsRaw/RDHUtils.h" - -#include -#include -#include -using namespace o2::framework; - -namespace o2 -{ -namespace fdd -{ -template -class RawDataReaderSpec : public Task -{ - public: - RawDataReaderSpec(const RawReader& rawReader) : mRawReader(rawReader) {} - RawDataReaderSpec() = default; - ~RawDataReaderSpec() override = default; - void init(InitContext& ic) final { o2::fdd::SingleLUT::Instance().printFullMap(); } - void run(ProcessingContext& pc) final - { - DPLRawParser parser(pc.inputs()); - mRawReader.clear(); - LOG(info) << "FDD RawDataReaderSpec"; - uint64_t count = 0; - for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { - //Proccessing each page - count++; - auto rdhPtr = reinterpret_cast(it.raw()); - gsl::span payload(it.data(), it.size()); - mRawReader.process(payload, o2::raw::RDHUtils::getLinkID(rdhPtr), int(0)); - } - LOG(info) << "Pages: " << count; - mRawReader.accumulateDigits(); - mRawReader.makeSnapshot(pc); - } - RawReader mRawReader; -}; - -template -framework::DataProcessorSpec getFDDRawDataReaderSpec(const RawReader& rawReader) -{ - LOG(info) << "DataProcessorSpec initDataProcSpec() for RawReaderFDD"; - std::vector outputSpec; - RawReader::prepareOutputSpec(outputSpec); - return DataProcessorSpec{ - "fdd-datareader-dpl", - o2::framework::select("TF:FDD/RAWDATA"), - outputSpec, - adaptFromTask>(rawReader), - Options{}}; -} - -} // namespace fdd -} // namespace o2 - -#endif /* O2_FDDDATAREADERDPL_H */ diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RecoWorkflow.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RecoWorkflow.h index 2dbd854e34eee..0d5d308216bb0 100644 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RecoWorkflow.h +++ b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RecoWorkflow.h @@ -20,7 +20,7 @@ namespace o2 { namespace fdd { -framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut); +framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap); } // namespace fdd } // namespace o2 #endif diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/ReconstructorSpec.h b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/ReconstructorSpec.h index 7dcb5d9aaba40..8f20ff1513ab4 100644 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/ReconstructorSpec.h +++ b/Detectors/FIT/FDD/workflow/include/FDDWorkflow/ReconstructorSpec.h @@ -18,6 +18,8 @@ #include "Framework/Task.h" #include "FDDReconstruction/Reconstructor.h" #include "DataFormatsFDD/RecPoint.h" +#include "DataFormatsFIT/DeadChannelMap.h" +#include "Framework/ConcreteDataMatcher.h" using namespace o2::framework; @@ -29,21 +31,25 @@ namespace fdd class FDDReconstructorDPL : public Task { public: - FDDReconstructorDPL(bool useMC) : mUseMC(useMC) {} + FDDReconstructorDPL(bool useMC, bool useDeadChannelMap) : mUseMC(useMC), mUseDeadChannelMap(useDeadChannelMap) {} ~FDDReconstructorDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; + void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; private: bool mUseMC = true; + bool mUseDeadChannelMap = true; + bool mUpdateDeadChannelMap = true; std::vector mRecPoints; std::vector mRecChData; + o2::fit::DeadChannelMap const* mDeadChannelMap; o2::fdd::Reconstructor mReco; o2::header::DataOrigin mOrigin = o2::header::gDataOriginFDD; }; /// create a processor spec -framework::DataProcessorSpec getFDDReconstructorSpec(bool useMC = true); +framework::DataProcessorSpec getFDDReconstructorSpec(bool useMC = true, bool useDeadChannelMap = true); } // namespace fdd } // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx index fb5b173fb7a94..43615b175734d 100644 --- a/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/EntropyDecoderSpec.cxx @@ -24,12 +24,12 @@ namespace o2 { namespace fdd { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FDD"); } @@ -73,7 +73,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"digits"}, "FDD", "DIGITSBC", 0, Lifetime::Timeframe}, @@ -82,17 +82,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_FDD", "FDD", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_FDD", "FDD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FDD/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_FDD", "FDD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FDD/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "fdd-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace fdd } // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/src/EntropyEncoderSpec.cxx b/Detectors/FIT/FDD/workflow/src/EntropyEncoderSpec.cxx index abb2518e5ae0b..be81f7ca7d3d4 100644 --- a/Detectors/FIT/FDD/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace fdd { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -69,12 +68,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("digits", "FDD", "DIGITSBC", 0, Lifetime::Timeframe); inputs.emplace_back("channels", "FDD", "DIGITSCH", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "FDD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FDD/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "FDD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FDD/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -82,13 +84,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) "fdd-entropy-encoder", inputs, Outputs{{"FDD", "CTFDATA", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace fdd } // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/src/RawDataProcessSpec.cxx b/Detectors/FIT/FDD/workflow/src/RawDataProcessSpec.cxx deleted file mode 100644 index bf18db67672c2..0000000000000 --- a/Detectors/FIT/FDD/workflow/src/RawDataProcessSpec.cxx +++ /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. - -/// @file RawDataProcessSpec.cxx - -#include "FDDWorkflow/RawDataProcessSpec.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace fdd -{ -using namespace std; -void RawDataProcessSpec::init(InitContext& ic) -{ -} - -void RawDataProcessSpec::run(ProcessingContext& pc) -{ - LOG(info) << "RawDataProcessSpec running..."; - auto vecDigits = pc.inputs().get>("digits"); - auto vecChannelData = pc.inputs().get>("digch"); - if (mDumpEventBlocks) { - DigitBlockFDD::print(vecDigits, vecChannelData); - } -} - -DataProcessorSpec getFDDRawDataProcessSpec(bool dumpProcessor) -{ - std::vector inputSpec; - inputSpec.emplace_back("digits", o2::header::gDataOriginFDD, "DIGITSBC", 0, Lifetime::Timeframe); - inputSpec.emplace_back("digch", o2::header::gDataOriginFDD, "DIGITSCH", 0, Lifetime::Timeframe); - LOG(info) << "DataProcessorSpec getRawDataProcessSpec"; - return DataProcessorSpec{ - "fdd-dataprocess-dpl-flp", - inputSpec, - Outputs{}, - AlgorithmSpec{adaptFromTask(dumpProcessor)}, - Options{}}; -} - -} // namespace fdd -} // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/src/RawWorkflow.cxx b/Detectors/FIT/FDD/workflow/src/RawWorkflow.cxx deleted file mode 100644 index c9e5c5be0c81d..0000000000000 --- a/Detectors/FIT/FDD/workflow/src/RawWorkflow.cxx +++ /dev/null @@ -1,42 +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 RawWorkflow.cxx - -#include "FDDWorkflow/RawWorkflow.h" -#include "FDDWorkflow/RawDataProcessSpec.h" -#include "FDDWorkflow/RawDataReaderSpec.h" -#include "FDDWorkflow/DigitWriterSpec.h" -#include "FDDWorkflow/RawReaderFDD.h" -namespace o2 -{ -namespace fdd -{ - -framework::WorkflowSpec getFDDRawWorkflow(bool useProcess, - bool dumpProcessor, bool dumpReader, - bool disableRootOut) -{ - LOG(info) << "framework::WorkflowSpec getFDDWorkflow"; - framework::WorkflowSpec specs; - specs.emplace_back(o2::fdd::getFDDRawDataReaderSpec(RawReaderFDD{dumpReader})); - - if (useProcess) { - specs.emplace_back(o2::fdd::getFDDRawDataProcessSpec(dumpProcessor)); - } - if (!disableRootOut) { - specs.emplace_back(o2::fdd::getFDDDigitWriterSpec(false, false)); - } - return specs; -} - -} // namespace fdd -} // namespace o2 diff --git a/Detectors/FIT/FDD/workflow/src/RecoWorkflow.cxx b/Detectors/FIT/FDD/workflow/src/RecoWorkflow.cxx index a7d4c15af81bb..b464e689f7a75 100644 --- a/Detectors/FIT/FDD/workflow/src/RecoWorkflow.cxx +++ b/Detectors/FIT/FDD/workflow/src/RecoWorkflow.cxx @@ -22,14 +22,14 @@ namespace o2 namespace fdd { -framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut) +framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap) { framework::WorkflowSpec specs; if (!disableRootInp) { specs.emplace_back(o2::fdd::getFDDDigitReaderSpec(useMC)); } - specs.emplace_back(o2::fdd::getFDDReconstructorSpec(useMC)); + specs.emplace_back(o2::fdd::getFDDReconstructorSpec(useMC, useDeadChannelMap)); if (!disableRootOut) { specs.emplace_back(o2::fdd::getFDDRecPointWriterSpec(useMC)); } diff --git a/Detectors/FIT/FDD/workflow/src/ReconstructorSpec.cxx b/Detectors/FIT/FDD/workflow/src/ReconstructorSpec.cxx index b7a0b9876a2ee..1d5d599b5ee31 100644 --- a/Detectors/FIT/FDD/workflow/src/ReconstructorSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/ReconstructorSpec.cxx @@ -18,6 +18,7 @@ #include "FDDWorkflow/ReconstructorSpec.h" #include "DataFormatsFDD/Digit.h" #include "DataFormatsFDD/MCLabel.h" +#include "Framework/CCDBParamSpec.h" using namespace o2::framework; @@ -44,6 +45,11 @@ void FDDReconstructorDPL::run(ProcessingContext& pc) // lblPtr = labels.get(); LOG(info) << "Ignoring MC info"; } + if (mUseDeadChannelMap && mUpdateDeadChannelMap) { + LOG(info) << "Populating reconsturctor object with Dead Channel Map object"; + auto deadChannelMap = pc.inputs().get("deadChannelMap"); + mReco.setDeadChannelMap(deadChannelMap.get()); + } int nDig = digitsBC.size(); mRecPoints.reserve(nDig); mRecChData.reserve(digitsCh.size()); @@ -58,16 +64,29 @@ void FDDReconstructorDPL::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, mRecChData); } -DataProcessorSpec getFDDReconstructorSpec(bool useMC) +void FDDReconstructorDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == ConcreteDataMatcher("FDD", "DeadChannelMap", 0)) { + mUpdateDeadChannelMap = false; + return; + } +} + +DataProcessorSpec getFDDReconstructorSpec(bool useMC, bool useDeadChannelMap) { std::vector inputSpec; std::vector outputSpec; inputSpec.emplace_back("digitsBC", o2::header::gDataOriginFDD, "DIGITSBC", 0, Lifetime::Timeframe); inputSpec.emplace_back("digitsCh", o2::header::gDataOriginFDD, "DIGITSCH", 0, Lifetime::Timeframe); + if (useMC) { LOG(info) << "Currently FDDReconstructor does not consume and provide MC truth"; // inputSpec.emplace_back("labels", o2::header::gDataOriginFDD, "DIGITSMCTR", 0, Lifetime::Timeframe); } + if (useDeadChannelMap) { + LOG(info) << "Dead channel map will be applied during reconstruction"; + inputSpec.emplace_back("deadChannelMap", o2::header::gDataOriginFDD, "DeadChannelMap", 0, Lifetime::Condition, ccdbParamSpec("FDD/Calib/DeadChannelMap")); + } outputSpec.emplace_back(o2::header::gDataOriginFDD, "RECPOINTS", 0, Lifetime::Timeframe); outputSpec.emplace_back(o2::header::gDataOriginFDD, "RECCHDATA", 0, Lifetime::Timeframe); @@ -75,7 +94,7 @@ DataProcessorSpec getFDDReconstructorSpec(bool useMC) "fdd-reconstructor", inputSpec, outputSpec, - AlgorithmSpec{adaptFromTask(useMC)}, + AlgorithmSpec{adaptFromTask(useMC, useDeadChannelMap)}, Options{}}; } diff --git a/Detectors/FIT/FDD/workflow/src/entropy-encoder-workflow.cxx b/Detectors/FIT/FDD/workflow/src/entropy-encoder-workflow.cxx index bcc42ebc2e086..b83e25557e760 100644 --- a/Detectors/FIT/FDD/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/FIT/FDD/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::fdd::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::fdd::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/FIT/FDD/workflow/src/fdd-reco-workflow.cxx b/Detectors/FIT/FDD/workflow/src/fdd-reco-workflow.cxx index 652ddb8bd2a29..888792425909b 100644 --- a/Detectors/FIT/FDD/workflow/src/fdd-reco-workflow.cxx +++ b/Detectors/FIT/FDD/workflow/src/fdd-reco-workflow.cxx @@ -38,6 +38,7 @@ void customize(std::vector& workflowOptions) {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}, {"disable-root-input", o2::framework::VariantType::Bool, false, {"disable root-files input readers"}}, {"disable-root-output", o2::framework::VariantType::Bool, false, {"disable root-files output writers"}}, + {"disable-dead-channel-map", o2::framework::VariantType::Bool, false, {"disable dead channel map"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); @@ -57,8 +58,9 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto useMC = !configcontext.options().get("disable-mc"); auto disableRootInp = configcontext.options().get("disable-root-input"); auto disableRootOut = configcontext.options().get("disable-root-output"); + bool useDeadChannelMap = !configcontext.options().get("disable-dead-channel-map"); - auto wf = o2::fdd::getRecoWorkflow(useMC, disableRootInp, disableRootOut); + auto wf = o2::fdd::getRecoWorkflow(useMC, disableRootInp, disableRootOut, useDeadChannelMap); // 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/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/calibration/CMakeLists.txt b/Detectors/FIT/FT0/calibration/CMakeLists.txt index d103b4a9a18b6..bee0493d300c1 100644 --- a/Detectors/FIT/FT0/calibration/CMakeLists.txt +++ b/Detectors/FIT/FT0/calibration/CMakeLists.txt @@ -10,26 +10,50 @@ # or submit itself to any jurisdiction. o2_add_library(FT0Calibration - SOURCES - src/FT0TimeOffsetSlotContainer.cxx - PUBLIC_LINK_LIBRARIES - O2::DataFormatsFT0 - O2::CommonDataFormat - O2::DetectorsCalibration - ) - o2_target_root_dictionary(FT0Calibration - HEADERS - include/FT0Calibration/FT0TimeOffsetSlotContainer.h - ) - o2_add_executable(ft0-time-offset-calib - COMPONENT_NAME calibration - SOURCES workflow/FT0TimeOffsetCalibration-Workflow.cxx - PUBLIC_LINK_LIBRARIES - O2::FT0Calibration O2::FITCalibration - ) - o2_add_executable(ft0-time-spectra-processor - COMPONENT_NAME calibration - SOURCES workflow/FT0TimeSpectraProcessor-Workflow.cxx - PUBLIC_LINK_LIBRARIES - O2::FT0Calibration - ) + SOURCES + src/FT0TimeOffsetSlotContainer.cxx + src/EventsPerBcCalibrator.cxx + PUBLIC_LINK_LIBRARIES + O2::DetectorsCalibration + O2::Framework + O2::CommonUtils + Microsoft.GSL::GSL + O2::DataFormatsFT0 + O2::CommonDataFormat + O2::Steer + O2::CCDB + ROOT::Minuit + ROOT::Hist + ) + +o2_target_root_dictionary(FT0Calibration + HEADERS + include/FT0Calibration/FT0TimeOffsetSlotContainer.h + include/FT0Calibration/EventsPerBcCalibrator.h + ) + +o2_add_executable(ft0-time-offset-calib + COMPONENT_NAME calibration + SOURCES + workflow/FT0TimeOffsetCalibration-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration O2::FITCalibration + ) + +o2_add_executable(ft0-time-spectra-processor + COMPONENT_NAME calibration + SOURCES + workflow/FT0TimeSpectraProcessor-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration + ) + +o2_add_executable(ft0-events-per-bc-processor + COMPONENT_NAME calibration + SOURCES + workflow/FT0EventsPerBcProcessor-Workflow.cxx + PUBLIC_LINK_LIBRARIES + O2::FT0Calibration + O2::Framework + O2::CCDB +) \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/README.md b/Detectors/FIT/FT0/calibration/README.md new file mode 100644 index 0000000000000..78b0f980400d2 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/README.md @@ -0,0 +1,62 @@ +# Calibrations + +## Events per BC Calibration +### Description +Generates histograms of **Events per Bunch Crossing (BC)**. Events can be filtered by applying amplitude thresholds to the **A-side** and **C-side**. + +### Command-Line Options +| Option | Default | Description | +| :--- | :--- | :--- | +| `--slot-len-sec` | `3600` | Duration of each slot in seconds. | +| `--slot-len-tf` | `0` | Slot length in Time Frames (TFs). | +| `--one-object-per-run` | — | If set, the workflow creates only one calibration object per run. | +| `--min-entries-number` | `0` | Minimum number of entries required for a slot to be valid. | +| `--min-ampl-side-a` | `-2147483648` | Amplitude threshold for Side A events. | +| `--min-ampl-side-c` | `-2147483648` | Amplitude threshold for Side C events. | + +--- + +## How to Run + +### Simulation Data +First, it is important to digitize data with a non-zero run number, orbit, and timestamp. To set these parameters, one can use the `--configKeyValues` option, as shown in the example below. +``` +o2-sim-digitizer-workflow \ +--onlyDet FT0 \ +--configKeyValues="HBFUtils.nHBFPerTF=128;HBFUtils.orbitFirst=128;HBFUtils.orbitFirstSampled=256;HBFUtils.runNumber=560560;HBFUtils.startTime=1768464099000" +``` + +To process simulation data, digits must first be converted to RAW format. The `o2-ft0-digi2raw` tool performs this conversion and generates the required configuration file. + +Once converted, you can run the calibration either as a single integrated workflow or by spawning as the sender and receiver components separately. + +#### Single Workflow Example +Execute the following command within the simulation directory: +``` +o2-raw-file-reader-workflow --input-conf FT0raw.cfg --loop -1 \ +| o2-ft0-flp-dpl-workflow --condition-backend=http://localhost:8080 \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080 +``` + +Sender example (in simulation directory): +``` +o2-raw-file-reader-workflow --input-conf FT0raw.cfg --loop -1 \ +| o2-ft0-flp-dpl-workflow --condition-backend=http://localhost:8080 \ +| o2-dpl-output-proxy --channel-config "name=downstream,method=connect,address=tcp://localhost:30453,type=push,transport=zeromq" --dataspec "downstream:FT0/DIGITSBC" +``` + +Receiver example: +``` +o2-dpl-raw-proxy --channel-config "name=readout-proxy,type=pull,method=bind,address=tcp://localhost:30453,rateLogging=1,transport=zeromq" --dataspec "A:FT0/DIGITSBC/0" \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10 --min-ampl-side-a=0" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080/ +``` + +### CTF Data +Example: +``` +o2-ctf-reader-workflow --ctf-input ctf.root --onlyDet FT0 \ +| o2-calibration-ft0-events-per-bc-processor --FT0EventsPerBcProcessor "--slot-len-sec=10" \ +| o2-calibration-ccdb-populator-workflow --ccdb-path=http://localhost:8080/ +``` \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h b/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h new file mode 100644 index 0000000000000..d831cc36201ab --- /dev/null +++ b/Detectors/FIT/FT0/calibration/include/FT0Calibration/EventsPerBcCalibrator.h @@ -0,0 +1,83 @@ +// 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_FT0TVXPERBCID +#define O2_FT0TVXPERBCID + +#include +#include +#include +#include + +#include "CommonDataFormat/FlatHisto2D.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsFT0/SpectraInfoObject.h" +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/EventsPerBc.h" +#include "DetectorsCalibration/TimeSlotCalibration.h" +#include "DetectorsCalibration/TimeSlot.h" +#include "CommonDataFormat/TFIDInfo.h" +#include "TH1F.h" +#include "Rtypes.h" + +namespace o2::ft0 +{ +struct EventsPerBcContainer { + EventsPerBcContainer(int32_t minAmplitudeSideA, int32_t minAmplitudeSideC, int32_t minSumOfAmplitude) : mMinAmplitudeSideA(minAmplitudeSideA), mMinAmplitudeSideC(minAmplitudeSideC), mMinSumOfAmplitude(minSumOfAmplitude) {} + + size_t getEntries() const { return entries; } + void print() const; + void fill(const o2::dataformats::TFIDInfo& ti, const gsl::span data); + void merge(const EventsPerBcContainer* prev); + + const int32_t mMinAmplitudeSideA; + const int32_t mMinAmplitudeSideC; + const int32_t mMinSumOfAmplitude; + + std::array mTvx{0.0}; + size_t entries{0}; + long startTimeStamp{0}; + long stopTimeStamp{0}; + + ClassDefNV(EventsPerBcContainer, 1); +}; + +class EventsPerBcCalibrator final : public o2::calibration::TimeSlotCalibration +{ + using Slot = o2::calibration::TimeSlot; + using TFType = o2::calibration::TFType; + using EventsHistogram = std::array; + + public: + EventsPerBcCalibrator(uint32_t minNumberOfEntries, int32_t minAmplitudeSideA, int32_t minAmplitudeSideC, int32_t minSumOfAmplitude); + + bool hasEnoughData(const Slot& slot) const override; + void initOutput() override; + void finalizeSlot(Slot& slot) override; + Slot& emplaceNewSlot(bool front, TFType tstart, TFType tend) override; + + const std::vector& getTvxPerBc() { return mTvxPerBcs; } + std::vector>& getTvxPerBcCcdbInfo() { return mTvxPerBcInfos; } + + private: + const uint32_t mMinNumberOfEntries; + const int32_t mMinAmplitudeSideA; + const int32_t mMinAmplitudeSideC; + const int32_t mMinSumOfAmplitude; + + std::vector mTvxPerBcs; + std::vector> mTvxPerBcInfos; + + ClassDefOverride(EventsPerBcCalibrator, 1); +}; +} // namespace o2::ft0 + +#endif diff --git a/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx b/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx new file mode 100644 index 0000000000000..b17c81213cd08 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/src/EventsPerBcCalibrator.cxx @@ -0,0 +1,81 @@ +// 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 "FT0Calibration/EventsPerBcCalibrator.h" +#include "CommonUtils/MemFileHelper.h" + +namespace o2::ft0 +{ +void EventsPerBcContainer::print() const +{ + LOG(info) << entries << " entries"; +} + +void EventsPerBcContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::span data) +{ + size_t oldEntries = entries; + for (const auto& digit : data) { + if (digit.mTriggers.getVertex() && digit.mTriggers.getAmplA() >= mMinAmplitudeSideA && digit.mTriggers.getAmplC() >= mMinAmplitudeSideC && (digit.mTriggers.getAmplA() + digit.mTriggers.getAmplC()) >= mMinSumOfAmplitude) { + mTvx[digit.mIntRecord.bc]++; + entries++; + } + } + LOG(debug) << "Container is filled with " << entries - oldEntries << " new events"; +} + +void EventsPerBcContainer::merge(const EventsPerBcContainer* prev) +{ + for (int bc = 0; bc < o2::constants::lhc::LHCMaxBunches; bc++) { + mTvx[bc] += prev->mTvx[bc]; + } + entries += prev->entries; +} + +void EventsPerBcCalibrator::initOutput() +{ + mTvxPerBcs.clear(); + mTvxPerBcInfos.clear(); +} + +EventsPerBcCalibrator::EventsPerBcCalibrator(uint32_t minNumberOfEntries, int32_t minAmplitudeSideA, int32_t minAmplitudeSideC, int32_t minSumOfAmplitude) : mMinNumberOfEntries(minNumberOfEntries), mMinAmplitudeSideA(minAmplitudeSideA), mMinAmplitudeSideC(minAmplitudeSideC), mMinSumOfAmplitude(minSumOfAmplitude) +{ + LOG(info) << "Defined threshold for number of entires per slot: " << mMinNumberOfEntries; + LOG(info) << "Defined threshold for side A amplitude for event: " << mMinAmplitudeSideA; + LOG(info) << "Defined threshold for side C amplitude for event: " << mMinAmplitudeSideC; +} + +bool EventsPerBcCalibrator::hasEnoughData(const EventsPerBcCalibrator::Slot& slot) const +{ + return slot.getContainer()->entries > mMinNumberOfEntries; +} + +void EventsPerBcCalibrator::finalizeSlot(EventsPerBcCalibrator::Slot& slot) +{ + LOG(info) << "Finalizing slot from " << slot.getStartTimeMS() << " to " << slot.getEndTimeMS(); + o2::ft0::EventsPerBcContainer* data = slot.getContainer(); + mTvxPerBcs.emplace_back(data->mTvx); + + auto clName = o2::utils::MemFileHelper::getClassName(mTvxPerBcs.back()); + auto flName = o2::ccdb::CcdbApi::generateFileName(clName); + + std::map metaData; + mTvxPerBcInfos.emplace_back(std::make_unique("FT0/Calib/EventsPerBc", clName, flName, metaData, slot.getStartTimeMS(), slot.getEndTimeMS())); + LOG(info) << "Created object valid from " << mTvxPerBcInfos.back()->getStartValidityTimestamp() << " to " << mTvxPerBcInfos.back()->getEndValidityTimestamp(); +} + +EventsPerBcCalibrator::Slot& EventsPerBcCalibrator::emplaceNewSlot(bool front, TFType tstart, TFType tend) +{ + auto& cont = getSlots(); + auto& slot = front ? cont.emplace_front(tstart, tend) : cont.emplace_back(tstart, tend); + slot.setContainer(std::make_unique(mMinAmplitudeSideA, mMinAmplitudeSideC, mMinSumOfAmplitude)); + return slot; +} +} // namespace o2::ft0 \ No newline at end of file diff --git a/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h b/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h index 49f72e8cbdfff..11b1ce25e9353 100644 --- a/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h +++ b/Detectors/FIT/FT0/calibration/src/FT0CalibrationLinkDef.h @@ -16,7 +16,9 @@ #pragma link off all functions; #pragma link C++ class o2::ft0::FT0TimeOffsetSlotContainer + ; +#pragma link C++ class o2::ft0::EventsPerBcCalibrator + ; #pragma link C++ class o2::calibration::TimeSlot < o2::ft0::FT0TimeOffsetSlotContainer>; #pragma link C++ class o2::calibration::TimeSlotCalibration < o2::ft0::FT0TimeOffsetSlotContainer>; - +#pragma link C++ class o2::calibration::TimeSlot < o2::ft0::EventsPerBcContainer> + ; +#pragma link C++ class o2::calibration::TimeSlotCalibration < o2::ft0::EventsPerBcContainer> + ; #endif diff --git a/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx new file mode 100644 index 0000000000000..38d634c20c828 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcProcessor-Workflow.cxx @@ -0,0 +1,49 @@ +// 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 "FT0EventsPerBcSpec.h" +#include "Framework/Lifetime.h" +#include + +o2::framework::WorkflowSpec defineDataProcessing(o2::framework::ConfigContext const& cfgc) +{ + using namespace o2::framework; + using o2::calibration::FT0EventsPerBcProcessor; + std::vector inputs; + inputs.emplace_back("digits", "FT0", "DIGITSBC", Lifetime::Timeframe); + auto ccdbRequest = std::make_shared(true, // orbitResetTime + false, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputs); + std::vector outputs; + outputs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBWrapper, "EventsPerBc"}, Lifetime::Timeframe); + outputs.emplace_back(ConcreteDataTypeMatcher{o2::calibration::Utils::gDataOriginCDBPayload, "EventsPerBc"}, Lifetime::Timeframe); + DataProcessorSpec dataProcessorSpec{ + "FT0EventsPerBcProcessor", + inputs, + outputs, + AlgorithmSpec(adaptFromTask(ccdbRequest)), + Options{ + {"save-to-file", VariantType::Bool, false, {"Save calibration object to local file"}}, + {"slot-len-sec", VariantType::UInt32, 3600u, {"Duration of each slot in seconds"}}, + {"one-object-per-run", VariantType::Bool, false, {"If set, workflow creates only one calibration object per run"}}, + {"min-entries-number", VariantType::UInt32, 5000u, {"Minimum number of entries required for a slot to be valid"}}, + {"min-ampl-side-a", VariantType::Int, 0, {"Amplitude threshold for Side A events"}}, + {"min-ampl-side-c", VariantType::Int, 0, {"Amplitude threshold for Side C events"}}, + {"min-sum-of-ampl", VariantType::Int, 0, {"Amplitude threshold for sum of A-side and C-side amplitudes"}}}}; + + WorkflowSpec workflow; + workflow.emplace_back(dataProcessorSpec); + return workflow; +} diff --git a/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h new file mode 100644 index 0000000000000..1d4d4a75842e8 --- /dev/null +++ b/Detectors/FIT/FT0/calibration/workflow/FT0EventsPerBcSpec.h @@ -0,0 +1,148 @@ +// 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_CALIBRATION_FT0_EVENTS_PER_BC_CALIBRATOR_H +#define O2_CALIBRATION_FT0_EVENTS_PER_BC_CALIBRATOR_H + +#include "Framework/runDataProcessing.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/ConfigParamSpec.h" +#include +#include "Framework/DeviceSpec.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/Task.h" +#include "DetectorsCalibration/Utils.h" +#include "DetectorsBase/GRPGeomHelper.h" + +#include "DataFormatsFT0/Digit.h" +#include "FT0Calibration/EventsPerBcCalibrator.h" + +namespace o2::calibration +{ +class FT0EventsPerBcProcessor final : public o2::framework::Task +{ + public: + FT0EventsPerBcProcessor(std::shared_ptr request) : mCCDBRequest(request) {} + + void init(o2::framework::InitContext& ic) final + { + o2::base::GRPGeomHelper::instance().setRequest(mCCDBRequest); + mSaveToFile = ic.options().get("save-to-file"); + + if (ic.options().hasOption("slot-len-sec")) { + mSlotLenSec = ic.options().get("slot-len-sec"); + } + if (ic.options().hasOption("one-object-per-run")) { + mOneObjectPerRun = ic.options().get("one-object-per-run"); + } + if (ic.options().hasOption("min-entries-number")) { + mMinNumberOfEntries = ic.options().get("min-entries-number"); + } + if (ic.options().hasOption("min-ampl-side-a")) { + mMinAmplitudeSideA = ic.options().get("min-ampl-side-a"); + } + if (ic.options().hasOption("min-ampl-side-c")) { + mMinAmplitudeSideC = ic.options().get("min-ampl-side-c"); + } + if (ic.options().hasOption("min-sum-of-ampl")) { + mMinSumOfAmplitude = ic.options().get("min-sum-of-ampl"); + } + + mCalibrator = std::make_unique(mMinNumberOfEntries, mMinAmplitudeSideA, mMinAmplitudeSideC, mMinSumOfAmplitude); + + if (mOneObjectPerRun) { + LOG(info) << "Only one object will be created at the end of run"; + mCalibrator->setUpdateAtTheEndOfRunOnly(); + } + if (mOneObjectPerRun == false) { + LOG(info) << "Defined slot interval to " << mSlotLenSec << " seconds"; + mCalibrator->setSlotLengthInSeconds(mSlotLenSec); + } + } + + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) + { + o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj); + } + + void run(o2::framework::ProcessingContext& pc) final + { + const auto& tinfo = pc.services().get(); + if (tinfo.globalRunNumberChanged || mRunNoFromDH < 1) { // new run is starting + mRunNoFromDH = tinfo.runNumber; + } + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + auto digits = pc.inputs().get>("digits"); + o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mCalibrator->getCurrentTFInfo()); + if (digits.size() == 0) { + return; + } + mCalibrator->process(digits); + if (mOneObjectPerRun == false) { + sendOutput(pc.outputs()); + } + } + + void endOfStream(o2::framework::EndOfStreamContext& ec) final + { + LOG(info) << "Received end-of-stream, checking for slot to finalize..."; + mCalibrator->checkSlotsToFinalize(); + sendOutput(ec.outputs()); + mCalibrator->initOutput(); + } + + void sendOutput(o2::framework::DataAllocator& output) + { + using o2::framework::Output; + const auto& tvxHists = mCalibrator->getTvxPerBc(); + auto& infos = mCalibrator->getTvxPerBcCcdbInfo(); + for (unsigned int idx = 0; idx < tvxHists.size(); idx++) { + auto& info = infos[idx]; + const auto& payload = tvxHists[idx]; + + auto image = o2::ccdb::CcdbApi::createObjectImage(&payload, info.get()); + LOG(info) << "Sending object " << info->getPath() << "/" << info->getFileName() << " of size " << image->size() + << " bytes, valid for " << info->getStartValidityTimestamp() << " : " << info->getEndValidityTimestamp(); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBPayload, "EventsPerBc", idx}, *image.get()); + output.snapshot(Output{o2::calibration::Utils::gDataOriginCDBWrapper, "EventsPerBc", idx}, *info.get()); + if (mSaveToFile) { + std::string fnout = fmt::format("ft0eventsPerBC_run_{}_{}_{}.root", mRunNoFromDH, info->getStartValidityTimestamp(), info->getEndValidityTimestamp()); + try { + TFile flout(fnout.c_str(), "recreate"); + flout.WriteObjectAny(&payload, "o2::ft0::EventsPerBc", o2::ccdb::CcdbApi::CCDBOBJECT_ENTRY); + LOGP(info, R"(Saved to file, can upload as: o2-ccdb-upload -f {} --starttimestamp {} --endtimestamp {} -k "ccdb_object" --path {} -m "runNumber={};AdjustableEOV=true;")", + fnout, info->getStartValidityTimestamp(), info->getEndValidityTimestamp(), info->getPath(), mRunNoFromDH); + flout.Close(); + } catch (const std::exception& ex) { + LOGP(error, "failed to store object to file {}, error: {}", fnout, ex.what()); + } + } + } + + if (tvxHists.size()) { + mCalibrator->initOutput(); + } + } + + private: + std::shared_ptr mCCDBRequest; + std::unique_ptr mCalibrator; + bool mOneObjectPerRun; + bool mSaveToFile = false; + int mRunNoFromDH = 0; + uint32_t mSlotLenSec; + uint32_t mMinNumberOfEntries; + int32_t mMinAmplitudeSideA; + int32_t mMinAmplitudeSideC; + int32_t mMinSumOfAmplitude; +}; +} // namespace o2::calibration +#endif diff --git a/Detectors/FIT/FT0/macros/CMakeLists.txt b/Detectors/FIT/FT0/macros/CMakeLists.txt index c4ed27d2513ba..17491ca4962c1 100644 --- a/Detectors/FIT/FT0/macros/CMakeLists.txt +++ b/Detectors/FIT/FT0/macros/CMakeLists.txt @@ -1,14 +1,21 @@ -# Copyright CERN and copyright holders of ALICE O2. This software is distributed -# under the terms of the GNU General Public License v3 (GPL Version 3), copied -# verbatim in the file "COPYING". +# 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. # -# See http://alice-o2.web.cern.ch/license for full licensing information. +# 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. +# granted to it by virtue of its status as an Intergovernmental Organization +# or submit itself to any jurisdiction. o2_add_test_root_macro(FT0Misaligner.C PUBLIC_LINK_LIBRARIES O2::CCDB O2::FT0Simulation LABELS ft0) + +o2_add_test_root_macro(FT0readEventsPerBc.C + PUBLIC_LINK_LIBRARIES + O2::CCDB + O2::DataFormatsFT0 + LABELS ft0) diff --git a/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C b/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C new file mode 100644 index 0000000000000..c6afc86389b9b --- /dev/null +++ b/Detectors/FIT/FT0/macros/FT0readEventsPerBc.C @@ -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. + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#endif + +#include "CCDB/CcdbApi.h" +#include "CCDB/CCDBTimeStampUtils.h" +#include "TH1F.h" +#include "DataFormatsFT0/EventsPerBc.h" +#include "Framework/Logger.h" +#include "CommonConstants/LHCConstants.h" + +std::unique_ptr hist; +std::unique_ptr canvas; + +void FT0readEventsPerBc(std::string ccdbUrl, long timestamp) +{ + o2::ccdb::CcdbApi ccdbApi; + ccdbApi.init(ccdbUrl); + const std::string ccdbPath = "FT0/Calib/EventsPerBc"; + std::map metadata; + + if (timestamp < 0) { + timestamp = o2::ccdb::getCurrentTimestamp(); + } + + std::unique_ptr events(ccdbApi.retrieveFromTFileAny(ccdbPath, metadata, timestamp)); + + if (!events) { + LOGP(fatal, "EventsPerBc object not found in {}/{} for timestamp {}.", ccdbUrl, ccdbPath, timestamp); + return; + } + + hist = std::make_unique("eventsPerBcHist", "Events per BC", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches - 1); + for (int idx = 0; idx < o2::constants::lhc::LHCMaxBunches; idx++) { + hist->Fill(idx, events->histogram[idx]); + } + canvas = std::make_unique(); + hist->Draw(); + canvas->Draw(); +} \ No newline at end of file diff --git a/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h index 4d749dbc90b42..41f11e303db67 100644 --- a/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h +++ b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CTFCoder.h @@ -34,10 +34,10 @@ namespace o2 namespace ft0 { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FT0) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FT0, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode digits to buffer with CTF @@ -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/reconstruction/include/FT0Reconstruction/CollisionTimeRecoTask.h b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CollisionTimeRecoTask.h index ff3f8384f488d..9f6cd500b9e74 100644 --- a/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CollisionTimeRecoTask.h +++ b/Detectors/FIT/FT0/reconstruction/include/FT0Reconstruction/CollisionTimeRecoTask.h @@ -21,6 +21,7 @@ #include "DataFormatsFT0/FT0ChannelTimeCalibrationObject.h" #include "DataFormatsFT0/SpectraInfoObject.h" #include "DataFormatsFT0/SlewingCoef.h" +#include "DataFormatsFIT/DeadChannelMap.h" #include #include #include @@ -57,10 +58,16 @@ class CollisionTimeRecoTask LOG(info) << "Init for slewing calib object"; mCalibSlew = calibSlew->makeSlewingPlots(); }; + void SetDeadChannelMap(const o2::fit::DeadChannelMap* deadChannelMap) + { + LOG(info) << "Updated dead channel map for CollisionTimeRecoTask"; + mDeadChannelMap = deadChannelMap; + } float getTimeInPS(const o2::ft0::ChannelData& channelData); private: o2::ft0::TimeSpectraInfoObject const* mTimeCalibObject = nullptr; + const o2::fit::DeadChannelMap* mDeadChannelMap = nullptr; typename o2::ft0::SlewingCoef::SlewingPlots_t mCalibSlew{}; }; } // namespace ft0 diff --git a/Detectors/FIT/FT0/reconstruction/src/CollisionTimeRecoTask.cxx b/Detectors/FIT/FT0/reconstruction/src/CollisionTimeRecoTask.cxx index 7363cef57cf31..3e3ffe52671e9 100644 --- a/Detectors/FIT/FT0/reconstruction/src/CollisionTimeRecoTask.cxx +++ b/Detectors/FIT/FT0/reconstruction/src/CollisionTimeRecoTask.cxx @@ -67,6 +67,10 @@ RP CollisionTimeRecoTask::processDigit(const o2::ft0::Digit& digit, // Reference channels shouldn't participate in reco at all! continue; } + if (mDeadChannelMap && !mDeadChannelMap->isChannelAlive(channelData.ChId)) { + LOG(debug) << "Channel " << channelData.ChId << " is dead - discarding data"; + continue; + } const float timeInPS = getTimeInPS(channelData); if (ChannelFilterParam::Instance().checkAll(channelData)) { outChData.emplace_back(channelData.ChId, timeInPS, (float)channelData.QTCAmpl, channelData.ChainQTC); 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/include/FT0Workflow/EntropyDecoderSpec.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyDecoderSpec.h index 4f8e8b5e9be63..d6009accfa45b 100644 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyDecoderSpec.h +++ b/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace ft0 class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace ft0 } // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyEncoderSpec.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyEncoderSpec.h index 8fd597af8629d..a1b3714fdbb26 100644 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyEncoderSpec.h +++ b/Detectors/FIT/FT0/workflow/include/FT0Workflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace ft0 class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace ft0 } // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataProcessDPLSpec.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataProcessDPLSpec.h deleted file mode 100644 index 7b7e98d50368e..0000000000000 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataProcessDPLSpec.h +++ /dev/null @@ -1,61 +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 FT0DataProcessDPLSpec.h - -#ifndef O2_FT0DATAPROCESSDPLSPEC_H -#define O2_FT0DATAPROCESSDPLSPEC_H - -#include "Framework/CallbackService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/Lifetime.h" -#include "Framework/Output.h" -#include "Framework/WorkflowSpec.h" -#include "Framework/SerializationMethods.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" - -#include "FT0Raw/DigitBlockFT0.h" -#include "DataFormatsFT0/Digit.h" -#include "DataFormatsFT0/ChannelData.h" - -#include -#include -#include - -using namespace o2::framework; - -namespace o2 -{ -namespace ft0 -{ - -class FT0DataProcessDPLSpec : public Task -{ - public: - FT0DataProcessDPLSpec(bool dumpEventBlocks) : mDumpEventBlocks(dumpEventBlocks) {} - ~FT0DataProcessDPLSpec() override = default; - void init(InitContext& ic) final; - void run(ProcessingContext& pc) final; - - private: - bool mDumpEventBlocks; - - o2::header::DataOrigin mOrigin = o2::header::gDataOriginFT0; -}; - -framework::DataProcessorSpec getFT0DataProcessDPLSpec(bool dumpProcessor); - -} // namespace ft0 -} // namespace o2 - -#endif /* O2_FT0DATAPROCESSDPL_H */ diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataReaderDPLSpec.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataReaderDPLSpec.h deleted file mode 100644 index 9074f4f7f0f34..0000000000000 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/FT0DataReaderDPLSpec.h +++ /dev/null @@ -1,110 +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 FT0DataReaderDPLSpec.h - -#ifndef O2_FT0DATAREADERDPLSPEC_H -#define O2_FT0DATAREADERDPLSPEC_H -#include "DataFormatsFT0/LookUpTable.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" -#include "Framework/CallbackService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/Lifetime.h" -#include "Framework/Output.h" -#include "Framework/WorkflowSpec.h" -#include "Framework/SerializationMethods.h" -#include "DPLUtils/DPLRawParser.h" -#include "Framework/InputRecordWalker.h" -#include -#include -#include -#include "CommonUtils/VerbosityConfig.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace ft0 -{ -template -class FT0DataReaderDPLSpec : public Task -{ - public: - FT0DataReaderDPLSpec(const RawReader& rawReader) : mRawReader(rawReader) {} - FT0DataReaderDPLSpec() = default; - ~FT0DataReaderDPLSpec() override = default; - typedef RawReader RawReader_t; - void init(InitContext& ic) final { o2::ft0::SingleLUT::Instance().printFullMap(); } - void run(ProcessingContext& pc) final - { - // 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 - { - static size_t contDeadBeef = 0; // number of times 0xDEADBEEF was seen continuously - std::vector dummy{InputSpec{"dummy", ConcreteDataMatcher{o2::header::gDataOriginFT0, o2::header::gDataDescriptionRawData, 0xDEADBEEF}}}; - for (const auto& ref : InputRecordWalker(pc.inputs(), dummy)) { - const auto dh = o2::framework::DataRefUtils::getHeader(ref); - auto payloadSize = DataRefUtils::getPayloadSize(ref); - if (payloadSize == 0) { - auto maxWarn = o2::conf::VerbosityConfig::Instance().maxWarnDeadBeef; - if (++contDeadBeef <= maxWarn) { - LOGP(alarm, "Found input [{}/{}/{:#x}] TF#{} 1st_orbit:{} Payload {} : assuming no payload for all links in this TF{}", - dh->dataOrigin.str, dh->dataDescription.str, dh->subSpecification, dh->tfCounter, dh->firstTForbit, payloadSize, - contDeadBeef == maxWarn ? fmt::format(". {} such inputs in row received, stopping reporting", contDeadBeef) : ""); - } - mRawReader.makeSnapshot(pc); // send empty output - return; - } - } - contDeadBeef = 0; // if good data, reset the counter - } - std::vector filter{InputSpec{"filter", ConcreteDataTypeMatcher{o2::header::gDataOriginFT0, o2::header::gDataDescriptionRawData}, Lifetime::Timeframe}}; - DPLRawParser parser(pc.inputs(), filter); - std::size_t count = 0; - for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { - //Proccessing each page - count++; - auto rdhPtr = reinterpret_cast(it.raw()); - gsl::span payload(it.data(), it.size()); - mRawReader.process(payload, o2::raw::RDHUtils::getLinkID(rdhPtr), o2::raw::RDHUtils::getEndPointID(rdhPtr)); - } - LOG(info) << "Pages: " << count; - mRawReader.accumulateDigits(); - mRawReader.makeSnapshot(pc); - mRawReader.clear(); - } - RawReader_t mRawReader; -}; - -template -framework::DataProcessorSpec getFT0DataReaderDPLSpec(const RawReader& rawReader, bool askSTFDist) -{ - LOG(info) << "DataProcessorSpec initDataProcSpec() for RawReaderFT0"; - std::vector outputSpec; - RawReader::prepareOutputSpec(outputSpec); - std::vector inputSpec{{"STF", ConcreteDataTypeMatcher{o2::header::gDataOriginFT0, "RAWDATA"}, Lifetime::Timeframe}}; - if (askSTFDist) { - inputSpec.emplace_back("STFDist", "FLP", "DISTSUBTIMEFRAME", 0, Lifetime::Timeframe); - } - return DataProcessorSpec{ - "ft0-datareader-dpl", - inputSpec, - outputSpec, - adaptFromTask>(rawReader), - Options{}}; -} - -} // namespace ft0 -} // namespace o2 - -#endif /* O2_FT0DATAREADERDPL_H */ diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/RawReaderFT0.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/RawReaderFT0.h deleted file mode 100644 index f7729394db652..0000000000000 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/RawReaderFT0.h +++ /dev/null @@ -1,156 +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 RawReaderFT0.h class for RAW data reading -// -// Artur.Furs -// afurs@cern.ch -// -//Main purpuse is to decode FT0 data blocks and push them to DigitBlockFT0 for proccess -//TODO: prepare wrappers for containers with digits and combine classes below into one template class? -#ifndef ALICEO2_FIT_RAWREADERFT0_H_ -#define ALICEO2_FIT_RAWREADERFT0_H_ -#include -#include -#include -#include "FT0Raw/RawReaderFT0Base.h" - -#include "DataFormatsFT0/Digit.h" -#include "DataFormatsFT0/ChannelData.h" - -#include "Framework/ProcessingContext.h" -#include "Framework/DataAllocator.h" -#include "Framework/OutputSpec.h" -#include - -namespace o2 -{ -namespace ft0 -{ -//Normal TCM mode -template -class RawReaderFT0 : public RawReaderFT0BaseNorm -{ - public: - RawReaderFT0(bool dumpData) : mDumpData(dumpData) {} - RawReaderFT0(const RawReaderFT0&) = default; - - RawReaderFT0() = default; - ~RawReaderFT0() = default; - static constexpr bool sUseTrgInput = useTrgInput; - void clear() - { - mVecDigits.clear(); - if constexpr (sUseTrgInput) { - mVecTriggerInput.clear(); - } - mVecChannelData.clear(); - } - void accumulateDigits() - { - if constexpr (sUseTrgInput) { - getDigits(mVecDigits, mVecChannelData, mVecTriggerInput); - } else { - getDigits(mVecDigits, mVecChannelData); - } - LOG(info) << "Number of Digits: " << mVecDigits.size(); - LOG(info) << "Number of ChannelData: " << mVecChannelData.size(); - if constexpr (sUseTrgInput) { - LOG(info) << "Number of TriggerInput: " << mVecTriggerInput.size(); - } - if (mDumpData) { - DigitBlockFT0::print(mVecDigits, mVecChannelData); - } - } - static void prepareOutputSpec(std::vector& outputSpec) - { - outputSpec.emplace_back(o2::header::gDataOriginFT0, "DIGITSBC", 0, o2::framework::Lifetime::Timeframe); - outputSpec.emplace_back(o2::header::gDataOriginFT0, "DIGITSCH", 0, o2::framework::Lifetime::Timeframe); - if constexpr (sUseTrgInput) { - outputSpec.emplace_back(o2::header::gDataOriginFT0, "TRIGGERINPUT", 0, o2::framework::Lifetime::Timeframe); - } - } - void makeSnapshot(o2::framework::ProcessingContext& pc) - { - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "DIGITSBC", 0}, mVecDigits); - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "DIGITSCH", 0}, mVecChannelData); - if constexpr (sUseTrgInput) { - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "TRIGGERINPUT", 0}, mVecTriggerInput); - } - } - bool mDumpData; - std::vector mVecDigits; - std::vector mVecTriggerInput; - std::vector mVecChannelData; -}; - -//Extended TCM mode (additional raw data struct) -template -class RawReaderFT0ext : public RawReaderFT0BaseExt -{ - public: - RawReaderFT0ext(bool dumpData) : mDumpData(dumpData) {} - RawReaderFT0ext(const RawReaderFT0ext&) = default; - static constexpr bool sUseTrgInput = useTrgInput; - RawReaderFT0ext() = default; - ~RawReaderFT0ext() = default; - void clear() - { - mVecDigits.clear(); - mVecChannelData.clear(); - mVecTrgExt.clear(); - if constexpr (sUseTrgInput) { - mVecTriggerInput.clear(); - } - } - void accumulateDigits() - { - if constexpr (sUseTrgInput) { - getDigits(mVecDigits, mVecChannelData, mVecTrgExt, mVecTriggerInput); - } else { - getDigits(mVecDigits, mVecChannelData, mVecTrgExt); - } - LOG(info) << "Number of Digits: " << mVecDigits.size(); - LOG(info) << "Number of ChannelData: " << mVecChannelData.size(); - LOG(info) << "Number of TriggerExt: " << mVecTrgExt.size(); - if (mDumpData) { - DigitBlockFT0ext::print(mVecDigits, mVecChannelData, mVecTrgExt); - } - } - static void prepareOutputSpec(std::vector& outputSpec) - { - outputSpec.emplace_back(o2::header::gDataOriginFT0, "DIGITSBC", 0, o2::framework::Lifetime::Timeframe); - outputSpec.emplace_back(o2::header::gDataOriginFT0, "DIGITSCH", 0, o2::framework::Lifetime::Timeframe); - outputSpec.emplace_back(o2::header::gDataOriginFT0, "DIGITSTRGEXT", 0, o2::framework::Lifetime::Timeframe); - if constexpr (sUseTrgInput) { - outputSpec.emplace_back(o2::header::gDataOriginFT0, "TRIGGERINPUT", 0, o2::framework::Lifetime::Timeframe); - } - } - void makeSnapshot(o2::framework::ProcessingContext& pc) - { - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "DIGITSBC", 0}, mVecDigits); - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "DIGITSCH", 0}, mVecChannelData); - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "DIGITSTRGEXT", 0}, mVecTrgExt); - if constexpr (sUseTrgInput) { - pc.outputs().snapshot(o2::framework::Output{o2::header::gDataOriginFT0, "TRIGGERINPUT", 0}, mVecTriggerInput); - } - } - bool mDumpData; - std::vector mVecDigits; - std::vector mVecChannelData; - std::vector mVecTrgExt; - std::vector mVecTriggerInput; -}; - -} // namespace ft0 -} // namespace o2 - -#endif diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/RecoWorkflow.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/RecoWorkflow.h index 3c6e4599a250c..6de23a1c66bfd 100644 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/RecoWorkflow.h +++ b/Detectors/FIT/FT0/workflow/include/FT0Workflow/RecoWorkflow.h @@ -20,7 +20,7 @@ namespace o2 { namespace ft0 { -framework::WorkflowSpec getRecoWorkflow(bool useMC, std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool disableRootInp, bool disableRootOut); +framework::WorkflowSpec getRecoWorkflow(bool useMC, std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap = true); } // namespace ft0 } // namespace o2 #endif diff --git a/Detectors/FIT/FT0/workflow/include/FT0Workflow/ReconstructionSpec.h b/Detectors/FIT/FT0/workflow/include/FT0Workflow/ReconstructionSpec.h index 1c671352e6ba7..307b2109fe35f 100644 --- a/Detectors/FIT/FT0/workflow/include/FT0Workflow/ReconstructionSpec.h +++ b/Detectors/FIT/FT0/workflow/include/FT0Workflow/ReconstructionSpec.h @@ -34,7 +34,7 @@ class ReconstructionDPL : public Task static constexpr int NCHANNELS = o2::ft0::Geometry::Nchannels; public: - ReconstructionDPL(bool useMC, const std::string& ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib) : mUseMC(useMC), mCCDBpath(ccdbpath), mUseTimeOffsetCalib(useTimeOffsetCalib), mUseSlewingCalib(useSlewingCalib) {} + ReconstructionDPL(bool useMC, const std::string& ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool useDeadChannelMap) : mUseMC(useMC), mCCDBpath(ccdbpath), mUseTimeOffsetCalib(useTimeOffsetCalib), mUseSlewingCalib(useSlewingCalib), mUseDeadChannelMap(useDeadChannelMap) {} ~ReconstructionDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -46,6 +46,8 @@ class ReconstructionDPL : public Task bool mUpdateCCDB = true; bool mUseTimeOffsetCalib = true; bool mUseSlewingCalib = true; + bool mUseDeadChannelMap = true; + bool mUpdateDeadChannelMap = true; const std::string mCCDBpath = o2::base::NameConf::getCCDBServer(); std::vector mRecPoints; std::vector mRecChData; @@ -55,7 +57,7 @@ class ReconstructionDPL : public Task }; /// create a processor spec -framework::DataProcessorSpec getReconstructionSpec(bool useMC = false, const std::string ccdbpath = "http://alice-ccdb.cern.ch", bool useTimeOffsetCalib = true, bool useSlewingCalib = true); +framework::DataProcessorSpec getReconstructionSpec(bool useMC = false, const std::string ccdbpath = "http://alice-ccdb.cern.ch", bool useTimeOffsetCalib = true, bool useSlewingCalib = true, bool useDeadChannelMap = true); } // namespace ft0 } // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx index 65d3585350888..97ea337705fee 100644 --- a/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/EntropyDecoderSpec.cxx @@ -24,12 +24,12 @@ namespace o2 { namespace ft0 { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FT0"); } @@ -73,7 +73,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"digits"}, "FT0", "DIGITSBC", 0, Lifetime::Timeframe}, @@ -82,16 +82,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_FT0", "FT0", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_FT0", "FT0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FT0/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_FT0", "FT0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FT0/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "ft0-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace ft0 } // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/src/EntropyEncoderSpec.cxx b/Detectors/FIT/FT0/workflow/src/EntropyEncoderSpec.cxx index 81bdc2e729bb4..7be6618a61103 100644 --- a/Detectors/FIT/FT0/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace ft0 { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -70,12 +69,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("digits", "FT0", "DIGITSBC", 0, Lifetime::Timeframe); inputs.emplace_back("channels", "FT0", "DIGITSCH", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "FT0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FT0/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "FT0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FT0/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -83,13 +85,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) "ft0-entropy-encoder", inputs, Outputs{{"FT0", "CTFDATA", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace ft0 } // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/src/FT0DataProcessDPLSpec.cxx b/Detectors/FIT/FT0/workflow/src/FT0DataProcessDPLSpec.cxx deleted file mode 100644 index d7a7a689d402f..0000000000000 --- a/Detectors/FIT/FT0/workflow/src/FT0DataProcessDPLSpec.cxx +++ /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. - -/// @file FT0DataProcessDPLSpec.cxx - -#include "FT0Workflow/FT0DataProcessDPLSpec.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace ft0 -{ -using namespace std; -void FT0DataProcessDPLSpec::init(InitContext& ic) -{ -} - -void FT0DataProcessDPLSpec::run(ProcessingContext& pc) -{ - LOG(info) << "FT0DataProcessDPLSpec running..."; - auto vecDigits = pc.inputs().get>("digits"); - auto vecChannelData = pc.inputs().get>("digch"); - if (mDumpEventBlocks) { - DigitBlockFT0::print(vecDigits, vecChannelData); - } -} - -DataProcessorSpec getFT0DataProcessDPLSpec(bool dumpProcessor) -{ - std::vector inputSpec; - inputSpec.emplace_back("digits", o2::header::gDataOriginFT0, "DIGITSBC", 0, Lifetime::Timeframe); - inputSpec.emplace_back("digch", o2::header::gDataOriginFT0, "DIGITSCH", 0, Lifetime::Timeframe); - LOG(info) << "DataProcessorSpec getFT0DataProcessDPLSpec"; - return DataProcessorSpec{ - "ft0-dataprocess-dpl-flp", - inputSpec, - Outputs{}, - AlgorithmSpec{adaptFromTask(dumpProcessor)}, - Options{}}; -} - -} // namespace ft0 -} // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/src/FT0Workflow.cxx b/Detectors/FIT/FT0/workflow/src/FT0Workflow.cxx deleted file mode 100644 index 156feb7dd3e2f..0000000000000 --- a/Detectors/FIT/FT0/workflow/src/FT0Workflow.cxx +++ /dev/null @@ -1,45 +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 FT0Workflow.cxx - -#include "FT0Workflow/FT0Workflow.h" -#include "FT0Workflow/FT0DataProcessDPLSpec.h" -#include "FT0Workflow/FT0DataReaderDPLSpec.h" -#include "FT0Workflow/FT0DigitWriterSpec.h" -#include "FT0Workflow/RawReaderFT0.h" -namespace o2 -{ -namespace ft0 -{ - -framework::WorkflowSpec getFT0Workflow(bool isExtendedMode, bool useProcess, - bool dumpProcessor, bool dumpReader, - bool disableRootOut, bool askSTFDist) -{ - LOG(info) << "framework::WorkflowSpec getFT0Workflow"; - framework::WorkflowSpec specs; - if (isExtendedMode) { - specs.emplace_back(o2::ft0::getFT0DataReaderDPLSpec(RawReaderFT0ext{dumpReader}, askSTFDist)); - } else { - specs.emplace_back(o2::ft0::getFT0DataReaderDPLSpec(RawReaderFT0{dumpReader}, askSTFDist)); - } - if (useProcess) { - specs.emplace_back(o2::ft0::getFT0DataProcessDPLSpec(dumpProcessor)); - } - if (!disableRootOut) { - specs.emplace_back(o2::ft0::getFT0DigitWriterSpec(false, false)); - } - return specs; -} - -} // namespace ft0 -} // namespace o2 diff --git a/Detectors/FIT/FT0/workflow/src/RecoWorkflow.cxx b/Detectors/FIT/FT0/workflow/src/RecoWorkflow.cxx index 247158164ac3b..2231011febd7f 100644 --- a/Detectors/FIT/FT0/workflow/src/RecoWorkflow.cxx +++ b/Detectors/FIT/FT0/workflow/src/RecoWorkflow.cxx @@ -22,13 +22,13 @@ namespace o2 namespace ft0 { -framework::WorkflowSpec getRecoWorkflow(bool useMC, std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool disableRootInp, bool disableRootOut) +framework::WorkflowSpec getRecoWorkflow(bool useMC, std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap) { framework::WorkflowSpec specs; if (!disableRootInp) { specs.emplace_back(o2::ft0::getDigitReaderSpec(useMC)); } - specs.emplace_back(o2::ft0::getReconstructionSpec(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib)); + specs.emplace_back(o2::ft0::getReconstructionSpec(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib, useDeadChannelMap)); if (!disableRootOut) { specs.emplace_back(o2::ft0::getRecPointWriterSpec(useMC)); } diff --git a/Detectors/FIT/FT0/workflow/src/ReconstructionSpec.cxx b/Detectors/FIT/FT0/workflow/src/ReconstructionSpec.cxx index 40bc96ebca58e..bc5217c8d7471 100644 --- a/Detectors/FIT/FT0/workflow/src/ReconstructionSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/ReconstructionSpec.cxx @@ -44,6 +44,7 @@ void ReconstructionDPL::init(InitContext& ic) LOG(info) << "FT0 param mMinRMS: " << CalibParam::Instance().mMinRMS; LOG(info) << "FT0 param mMaxSigma: " << CalibParam::Instance().mMaxSigma; LOG(info) << "FT0 param mMaxDiffMean: " << CalibParam::Instance().mMaxDiffMean; + LOG(info) << "FT0 dead channel map will be applied " << mUseDeadChannelMap; } void ReconstructionDPL::run(ProcessingContext& pc) @@ -69,6 +70,12 @@ void ReconstructionDPL::run(ProcessingContext& pc) mReco.SetSlewingCalibObject(slewingCalibObject.get()); } + if (mUseDeadChannelMap && mUpdateDeadChannelMap) { + LOG(debug) << "Applying dead channel map"; + auto deadChannelMap = pc.inputs().get("deadChannelMap"); + mReco.SetDeadChannelMap(deadChannelMap.get()); + } + mRecPoints.reserve(digits.size()); mRecChData.reserve(channels.size()); mReco.processTF(digits, channels, mRecPoints, mRecChData); @@ -91,6 +98,11 @@ void ReconstructionDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) mUseSlewingCalib = false; // upload only once, slewing should be stable during the run return; } + if (matcher == ConcreteDataMatcher("FT0", "DeadChannelMap", 0)) { + LOG(debug) << "New DeadChannelMap is uploaded"; + mUpdateDeadChannelMap = false; + return; + } } void ReconstructionDPL::endOfStream(EndOfStreamContext& ec) @@ -99,12 +111,13 @@ void ReconstructionDPL::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib) +DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath, bool useTimeOffsetCalib, bool useSlewingCalib, bool useDeadChannelMap) { std::vector inputSpec; std::vector outputSpec; inputSpec.emplace_back("digits", o2::header::gDataOriginFT0, "DIGITSBC", 0, Lifetime::Timeframe); inputSpec.emplace_back("digch", o2::header::gDataOriginFT0, "DIGITSCH", 0, Lifetime::Timeframe); + if (useMC) { LOG(info) << "Currently Reconstruction does not consume and provide MC truth"; inputSpec.emplace_back("labels", o2::header::gDataOriginFT0, "DIGITSMCTR", 0, Lifetime::Timeframe); @@ -121,6 +134,11 @@ DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath, ccdbParamSpec("FT0/Calib/SlewingCoef")); } + if (useDeadChannelMap) { + LOG(info) << "Dead channel map will be applied during reconstruction"; + inputSpec.emplace_back("deadChannelMap", o2::header::gDataOriginFT0, "DeadChannelMap", 0, Lifetime::Condition, ccdbParamSpec("FT0/Calib/DeadChannelMap")); + } + outputSpec.emplace_back(o2::header::gDataOriginFT0, "RECPOINTS", 0, Lifetime::Timeframe); outputSpec.emplace_back(o2::header::gDataOriginFT0, "RECCHDATA", 0, Lifetime::Timeframe); @@ -128,7 +146,7 @@ DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath, "ft0-reconstructor", inputSpec, outputSpec, - AlgorithmSpec{adaptFromTask(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib)}, + AlgorithmSpec{adaptFromTask(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib, useDeadChannelMap)}, Options{}}; } diff --git a/Detectors/FIT/FT0/workflow/src/entropy-encoder-workflow.cxx b/Detectors/FIT/FT0/workflow/src/entropy-encoder-workflow.cxx index 6a98bbdafd53b..144d27abeda9c 100644 --- a/Detectors/FIT/FT0/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/FIT/FT0/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::ft0::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::ft0::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/FIT/FT0/workflow/src/ft0-reco-workflow.cxx b/Detectors/FIT/FT0/workflow/src/ft0-reco-workflow.cxx index 3e6a6bf5da090..ab39068aedb38 100644 --- a/Detectors/FIT/FT0/workflow/src/ft0-reco-workflow.cxx +++ b/Detectors/FIT/FT0/workflow/src/ft0-reco-workflow.cxx @@ -41,7 +41,8 @@ void customize(std::vector& workflowOptions) {"disable-root-output", o2::framework::VariantType::Bool, false, {"disable root-files output writers"}}, {"disable-time-offset-calib", o2::framework::VariantType::Bool, false, {"disable timeoffset calibration"}}, {"disable-slewing-calib", o2::framework::VariantType::Bool, false, {"disable slewing calibration"}}, - {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; + {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, + {"disable-dead-channel-map", VariantType::Bool, false, {"disable dead channel map"}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); } @@ -64,9 +65,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto disableRootOut = configcontext.options().get("disable-root-output"); const auto useTimeOffsetCalib = !configcontext.options().get("disable-time-offset-calib"); const auto useSlewingCalib = !configcontext.options().get("disable-slewing-calib"); + const auto useDeadChannelMap = !configcontext.options().get("disable-dead-channel-map"); LOG(info) << "WorkflowSpec getRecoWorkflow useMC " << useMC << " CCDB " << ccdbpath; - auto wf = o2::ft0::getRecoWorkflow(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib, disableRootInp, disableRootOut); + auto wf = o2::ft0::getRecoWorkflow(useMC, ccdbpath, useTimeOffsetCalib, useSlewingCalib, disableRootInp, disableRootOut, useDeadChannelMap); // 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/FIT/FV0/reconstruction/include/FV0Reconstruction/BaseRecoTask.h b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/BaseRecoTask.h index 12d89b82a13cc..c5cb5b0da6d05 100644 --- a/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/BaseRecoTask.h +++ b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/BaseRecoTask.h @@ -18,6 +18,7 @@ #include "DataFormatsFV0/ChannelData.h" #include "DataFormatsFV0/RecPoints.h" #include "DataFormatsFV0/FV0ChannelTimeCalibrationObject.h" +#include "DataFormatsFIT/DeadChannelMap.h" #include namespace o2 @@ -33,14 +34,15 @@ class BaseRecoTask ~BaseRecoTask() = default; o2::fv0::RecPoints process(o2::fv0::Digit const& bcd, gsl::span inChData, - gsl::span outChData); + std::vector& outChData); void FinishTask(); void SetChannelOffset(o2::fv0::FV0ChannelTimeCalibrationObject const* caliboffsets) { mCalibOffset = caliboffsets; }; + void SetDeadChannelMap(o2::fit::DeadChannelMap const* deadChannelMap) { mDeadChannelMap = deadChannelMap; } int getOffset(int channel); private: o2::fv0::FV0ChannelTimeCalibrationObject const* mCalibOffset = nullptr; - + o2::fit::DeadChannelMap const* mDeadChannelMap = nullptr; ClassDefNV(BaseRecoTask, 3); }; } // namespace fv0 diff --git a/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h index cbec444ef11be..082fbd93a705a 100644 --- a/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h +++ b/Detectors/FIT/FV0/reconstruction/include/FV0Reconstruction/CTFCoder.h @@ -30,10 +30,10 @@ namespace o2 namespace fv0 { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FV0) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::FV0, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode digits to buffer with CTF @@ -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/reconstruction/src/BaseRecoTask.cxx b/Detectors/FIT/FV0/reconstruction/src/BaseRecoTask.cxx index 8a217232592df..8032220f8996d 100644 --- a/Detectors/FIT/FV0/reconstruction/src/BaseRecoTask.cxx +++ b/Detectors/FIT/FV0/reconstruction/src/BaseRecoTask.cxx @@ -27,7 +27,7 @@ using RP = o2::fv0::RecPoints; RP BaseRecoTask::process(o2::fv0::Digit const& bcd, gsl::span inChData, - gsl::span outChData) + std::vector& outChData) { LOG(debug) << "Running reconstruction on new event"; @@ -44,22 +44,27 @@ RP BaseRecoTask::process(o2::fv0::Digit const& bcd, int nch = inChData.size(); for (int ich = 0; ich < nch; ich++) { LOG(debug) << " channel " << ich << " / " << nch; + if (mDeadChannelMap && !mDeadChannelMap->isChannelAlive(inChData[ich].ChId)) { + LOG(debug) << "Channel " << ich << " is dead - discarding data"; + continue; + } int offsetChannel = getOffset(int(inChData[ich].ChId)); - outChData[ich] = o2::fv0::ChannelDataFloat{inChData[ich].ChId, - (inChData[ich].CFDTime - offsetChannel) * DigitizationConstant::TIME_PER_TDCCHANNEL, - (float)inChData[ich].QTCAmpl, - inChData[ich].ChainQTC}; + outChData.emplace_back(o2::fv0::ChannelDataFloat{inChData[ich].ChId, + (inChData[ich].CFDTime - offsetChannel) * DigitizationConstant::TIME_PER_TDCCHANNEL, + (float)inChData[ich].QTCAmpl, + inChData[ich].ChainQTC}); + const auto& currentOutCh = outChData.back(); // Conditions for reconstructing collision time (3 variants: first, average-relaxed and average-tight) - if (outChData[ich].charge > FV0DigParam::Instance().chargeThrForMeanTime) { - sideAtimeFirst = std::min(static_cast(sideAtimeFirst), outChData[ich].time); + if (currentOutCh.charge > FV0DigParam::Instance().chargeThrForMeanTime) { + sideAtimeFirst = std::min(static_cast(sideAtimeFirst), currentOutCh.time); if (inChData[ich].areAllFlagsGood()) { - if (std::abs(outChData[ich].time) < FV0DigParam::Instance().mTimeThresholdForReco) { - sideAtimeAvg += outChData[ich].time; + if (std::abs(currentOutCh.time) < FV0DigParam::Instance().mTimeThresholdForReco) { + sideAtimeAvg += currentOutCh.time; ndigitsA++; } - if (outChData[ich].charge > FV0DigParam::Instance().mAmpThresholdForReco && std::abs(outChData[ich].time) < FV0DigParam::Instance().mTimeThresholdForReco) { - sideAtimeAvgSelected += outChData[ich].time; + if (currentOutCh.charge > FV0DigParam::Instance().mAmpThresholdForReco && std::abs(currentOutCh.time) < FV0DigParam::Instance().mTimeThresholdForReco) { + sideAtimeAvgSelected += currentOutCh.time; ndigitsASelected++; } } diff --git a/Detectors/FIT/FV0/simulation/include/FV0Simulation/Digitizer.h b/Detectors/FIT/FV0/simulation/include/FV0Simulation/Digitizer.h index 6956d8126ce53..b97893822c9d8 100644 --- a/Detectors/FIT/FV0/simulation/include/FV0Simulation/Digitizer.h +++ b/Detectors/FIT/FV0/simulation/include/FV0Simulation/Digitizer.h @@ -14,6 +14,7 @@ #include "CommonDataFormat/InteractionRecord.h" #include "DataFormatsFV0/Digit.h" +#include "DataFormatsFIT/DeadChannelMap.h" #include "DataFormatsFV0/ChannelData.h" #include "DataFormatsFV0/MCLabel.h" #include "FV0Simulation/Detector.h" @@ -51,6 +52,7 @@ class Digitizer void setEventId(Int_t id) { mEventId = id; } void setSrcId(Int_t id) { mSrcId = id; } void setInteractionRecord(const InteractionTimeRecord& ir) { mIntRecord = ir; } + void setDeadChannelMap(o2::fit::DeadChannelMap const* deadChannelMap) { mDeadChannelMap = deadChannelMap; }; void process(const std::vector& hits, std::vector& digitsBC, std::vector& digitsCh, std::vector& digitsTrig, @@ -132,6 +134,8 @@ class Digitizer BCCache mLastBCCache; // buffer for the last BC std::array mCfdStartIndex; // start indices for the CFD detector + o2::fit::DeadChannelMap const* mDeadChannelMap = nullptr; + /// Internal helper methods related to conversion of energy-deposition into el. signal Int_t SimulateLightYield(Int_t pmt, Int_t nPhot) const; Float_t SimulateTimeCfd(int& startIndex, const ChannelDigitF& pulseLast, const ChannelDigitF& pulse) const; 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 1c94b14f029cf..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++) { @@ -98,6 +98,11 @@ void Digitizer::process(const std::vector& hits, for (auto ids : hitIdx) { const auto& hit = hits[ids]; Int_t detId = hit.GetDetectorID(); + + if (mDeadChannelMap && !mDeadChannelMap->isChannelAlive(detId)) { + continue; + } + Double_t hitEdep = hit.GetHitValue() * 1e3; // convert to MeV Float_t const hitTime = hit.GetTime() * 1e9; // convert to ns // TODO: check how big is inaccuracy if more than 1 'below-threshold' particles hit the same detector cell @@ -144,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, @@ -195,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]); @@ -233,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; @@ -280,8 +287,10 @@ void Digitizer::storeBC(const BCCache& bc, avgTime += iCfdZero; if (iPmt < 24) { nSignalInner++; + totalChargeInnerRing += iTotalCharge; } else { nSignalOuter++; + totalChargeOuterRing += iTotalCharge; } } } @@ -295,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; @@ -309,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); } @@ -337,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; } @@ -395,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/include/FV0Workflow/EntropyDecoderSpec.h b/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyDecoderSpec.h index 67b74f45e42bf..76f1aae5e728d 100644 --- a/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyDecoderSpec.h +++ b/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace fv0 class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace fv0 } // namespace o2 diff --git a/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyEncoderSpec.h b/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyEncoderSpec.h index db4f154a302c7..0df9403a88a12 100644 --- a/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyEncoderSpec.h +++ b/Detectors/FIT/FV0/workflow/include/FV0Workflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace fv0 class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace fv0 } // namespace o2 diff --git a/Detectors/FIT/FV0/workflow/include/FV0Workflow/RecoWorkflow.h b/Detectors/FIT/FV0/workflow/include/FV0Workflow/RecoWorkflow.h index 015870d9178e2..f035b2406e5ba 100644 --- a/Detectors/FIT/FV0/workflow/include/FV0Workflow/RecoWorkflow.h +++ b/Detectors/FIT/FV0/workflow/include/FV0Workflow/RecoWorkflow.h @@ -20,7 +20,7 @@ namespace o2 { namespace fv0 { -framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut); +framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap); } // namespace fv0 } // namespace o2 #endif diff --git a/Detectors/FIT/FV0/workflow/include/FV0Workflow/ReconstructionSpec.h b/Detectors/FIT/FV0/workflow/include/FV0Workflow/ReconstructionSpec.h index d71e154280e3d..934ce4d2c4a66 100644 --- a/Detectors/FIT/FV0/workflow/include/FV0Workflow/ReconstructionSpec.h +++ b/Detectors/FIT/FV0/workflow/include/FV0Workflow/ReconstructionSpec.h @@ -34,7 +34,7 @@ class ReconstructionDPL : public Task static constexpr int NCHANNELS = o2::fv0::Constants::nFv0Channels; public: - ReconstructionDPL(bool useMC, const std::string ccdbpath) : mUseMC(useMC), mCCDBpath(ccdbpath) {} + ReconstructionDPL(bool useMC, bool useDeadChannelMap, const std::string ccdbpath) : mUseMC(useMC), mUseDeadChannelMap(useDeadChannelMap), mCCDBpath(ccdbpath) {} ~ReconstructionDPL() override = default; void init(InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -44,6 +44,8 @@ class ReconstructionDPL : public Task private: bool mUseMC = false; bool mUpdateCCDB = true; + bool mUseDeadChannelMap = true; + bool mUpdateDeadChannelMap = true; const std::string mCCDBpath = o2::base::NameConf::getCCDBServer(); std::vector mRecPoints; std::vector mRecChData; @@ -53,7 +55,7 @@ class ReconstructionDPL : public Task }; /// create a processor spec -framework::DataProcessorSpec getReconstructionSpec(bool useMC = false, const std::string ccdbpath = "http://alice-ccdb.cern.ch"); +framework::DataProcessorSpec getReconstructionSpec(bool useMC = false, bool useDeadChannelMap = true, const std::string ccdbpath = "http://alice-ccdb.cern.ch"); } // namespace fv0 } // namespace o2 diff --git a/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx b/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx index 9310905ad41b9..6cf8043cf683f 100644 --- a/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/EntropyDecoderSpec.cxx @@ -24,12 +24,12 @@ namespace o2 { namespace fv0 { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); mCTFCoder.setVerbosity(verbosity); + mCTFCoder.setSupportBCShifts(true); mCTFCoder.setDictBinding("ctfdict_FV0"); } @@ -73,7 +73,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"digits"}, "FV0", "DIGITSBC", 0, Lifetime::Timeframe}, @@ -82,17 +82,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_FV0", "FV0", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_FV0", "FV0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_FV0", "FV0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "fv0-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace fv0 } // namespace o2 diff --git a/Detectors/FIT/FV0/workflow/src/EntropyEncoderSpec.cxx b/Detectors/FIT/FV0/workflow/src/EntropyEncoderSpec.cxx index a25c16a5d697c..2448af09fac4e 100644 --- a/Detectors/FIT/FV0/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace fv0 { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -71,12 +70,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("digits", "FV0", "DIGITSBC", 0, Lifetime::Timeframe); inputs.emplace_back("channels", "FV0", "DIGITSCH", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "FV0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "FV0", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -85,13 +87,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"FV0", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "FV0", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace fv0 } // namespace o2 diff --git a/Detectors/FIT/FV0/workflow/src/RecoWorkflow.cxx b/Detectors/FIT/FV0/workflow/src/RecoWorkflow.cxx index 6bfc5479303d1..a0ef71b75765a 100644 --- a/Detectors/FIT/FV0/workflow/src/RecoWorkflow.cxx +++ b/Detectors/FIT/FV0/workflow/src/RecoWorkflow.cxx @@ -22,14 +22,13 @@ namespace o2 namespace fv0 { -framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut) +framework::WorkflowSpec getRecoWorkflow(bool useMC, bool disableRootInp, bool disableRootOut, bool useDeadChannelMap) { framework::WorkflowSpec specs; if (!disableRootInp) { specs.emplace_back(o2::fv0::getDigitReaderSpec(useMC)); } - - specs.emplace_back(o2::fv0::getReconstructionSpec(useMC)); + specs.emplace_back(o2::fv0::getReconstructionSpec(useMC, useDeadChannelMap)); if (!disableRootOut) { specs.emplace_back(o2::fv0::getRecPointWriterSpec(useMC)); } diff --git a/Detectors/FIT/FV0/workflow/src/ReconstructionSpec.cxx b/Detectors/FIT/FV0/workflow/src/ReconstructionSpec.cxx index 520ac4dbaa563..cdf297b334588 100644 --- a/Detectors/FIT/FV0/workflow/src/ReconstructionSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/ReconstructionSpec.cxx @@ -21,6 +21,7 @@ #include "DataFormatsFV0/ChannelData.h" #include "DataFormatsFV0/MCLabel.h" #include "DataFormatsFV0/FV0ChannelTimeCalibrationObject.h" +#include "DataFormatsFIT/DeadChannelMap.h" #include "Framework/CCDBParamSpec.h" using namespace o2::framework; @@ -41,6 +42,7 @@ void ReconstructionDPL::run(ProcessingContext& pc) { mTimer.Start(false); mRecPoints.clear(); + mRecChData.clear(); auto digits = pc.inputs().get>("digits"); auto digch = pc.inputs().get>("digch"); // RS: if we need to process MC truth, uncomment lines below @@ -53,18 +55,19 @@ void ReconstructionDPL::run(ProcessingContext& pc) auto caliboffsets = pc.inputs().get("fv0offsets"); mReco.SetChannelOffset(caliboffsets.get()); } + if (mUseDeadChannelMap && mUpdateDeadChannelMap) { + auto deadChannelMap = pc.inputs().get("deadChannelMap"); + mReco.SetDeadChannelMap(deadChannelMap.get()); + } int nDig = digits.size(); LOG(debug) << " nDig " << nDig << " | ndigch " << digch.size(); mRecPoints.reserve(nDig); - mRecChData.resize(digch.size()); for (int id = 0; id < nDig; id++) { const auto& digit = digits[id]; LOG(debug) << " ndig " << id << " bc " << digit.getBC() << " orbit " << digit.getOrbit(); auto channels = digit.getBunchChannelData(digch); - gsl::span out_ch(mRecChData); - out_ch = out_ch.subspan(digit.ref.getFirstEntry(), digit.ref.getEntries()); - mRecPoints.emplace_back(mReco.process(digit, channels, out_ch)); + mRecPoints.emplace_back(mReco.process(digit, channels, mRecChData)); } LOG(debug) << "FV0 reconstruction pushes " << mRecPoints.size() << " RecPoints"; @@ -80,6 +83,9 @@ void ReconstructionDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) mUpdateCCDB = false; return; } + if (matcher == ConcreteDataMatcher(o2::header::gDataOriginFV0, "DeadChannelMap", 0)) { + mUpdateDeadChannelMap = false; + } } void ReconstructionDPL::endOfStream(EndOfStreamContext& ec) @@ -88,7 +94,7 @@ void ReconstructionDPL::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath) +DataProcessorSpec getReconstructionSpec(bool useMC, bool useDeadChannelMap, const std::string ccdbpath) { std::vector inputSpec; std::vector outputSpec; @@ -98,6 +104,10 @@ DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath) LOG(info) << "Currently Reconstruction does not consume and provide MC truth"; inputSpec.emplace_back("labels", o2::header::gDataOriginFV0, "DIGITSMCTR", 0, Lifetime::Timeframe); } + if (useDeadChannelMap) { + LOG(info) << "Dead channel map will be applied during reconstruction"; + inputSpec.emplace_back("deadChannelMap", o2::header::gDataOriginFV0, "DeadChannelMap", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/DeadChannelMap")); + } inputSpec.emplace_back("fv0offsets", "FV0", "TimeOffset", 0, Lifetime::Condition, ccdbParamSpec("FV0/Calib/ChannelTimeOffset")); @@ -109,7 +119,7 @@ DataProcessorSpec getReconstructionSpec(bool useMC, const std::string ccdbpath) "fv0-reconstructor", inputSpec, outputSpec, - AlgorithmSpec{adaptFromTask(useMC, ccdbpath)}, + AlgorithmSpec{adaptFromTask(useMC, useDeadChannelMap, ccdbpath)}, Options{}}; } diff --git a/Detectors/FIT/FV0/workflow/src/entropy-encoder-workflow.cxx b/Detectors/FIT/FV0/workflow/src/entropy-encoder-workflow.cxx index 90f37996b55b7..932e0a37ee376 100644 --- a/Detectors/FIT/FV0/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/FIT/FV0/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::fv0::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::fv0::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/FIT/FV0/workflow/src/fv0-reco-workflow.cxx b/Detectors/FIT/FV0/workflow/src/fv0-reco-workflow.cxx index 16d1383c7e8c4..309560e2d6b36 100644 --- a/Detectors/FIT/FV0/workflow/src/fv0-reco-workflow.cxx +++ b/Detectors/FIT/FV0/workflow/src/fv0-reco-workflow.cxx @@ -39,6 +39,7 @@ void customize(std::vector& workflowOptions) {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}, {"disable-root-input", o2::framework::VariantType::Bool, false, {"disable root-files input readers"}}, {"disable-root-output", o2::framework::VariantType::Bool, false, {"disable root-files output writers"}}, + {"disable-dead-channel-map", o2::framework::VariantType::Bool, false, {"disable dead channel map"}}, {"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}}; o2::raw::HBFUtilsInitializer::addConfigOption(options); std::swap(workflowOptions, options); @@ -59,9 +60,10 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto useMC = !configcontext.options().get("disable-mc"); auto disableRootInp = configcontext.options().get("disable-root-input"); auto disableRootOut = configcontext.options().get("disable-root-output"); + bool useDeadChannelMap = !configcontext.options().get("disable-dead-channel-map"); LOG(info) << "WorkflowSpec getRecoWorkflow useMC " << useMC; - auto wf = o2::fv0::getRecoWorkflow(useMC, disableRootInp, disableRootOut); + auto wf = o2::fv0::getRecoWorkflow(useMC, disableRootInp, disableRootOut, useDeadChannelMap); // 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/FOCAL/simulation/data/simcuts.dat b/Detectors/FOCAL/simulation/data/simcuts.dat index 744f67c3c81f4..870e38182f01c 100644 --- a/Detectors/FOCAL/simulation/data/simcuts.dat +++ b/Detectors/FOCAL/simulation/data/simcuts.dat @@ -14,3 +14,5 @@ FOC 3 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 -1. - FOC 6 5.e-5 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 -1. -1 -1 -1 -1 1 -1 3 -1 -1 -1 -1 -1 * Aluminium FOC 11 5.e-5 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 -1. -1 -1 -1 -1 1 -1 3 -1 -1 -1 -1 -1 +* Air +FOC 13 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 1.e-4 -1. -1 -1 -1 -1 1 -1 3 -1 -1 -1 -1 -1 diff --git a/Detectors/Filtering/src/FilteringSpec.cxx b/Detectors/Filtering/src/FilteringSpec.cxx index 847fa2cf7e1e5..ea82b1456d955 100644 --- a/Detectors/Filtering/src/FilteringSpec.cxx +++ b/Detectors/Filtering/src/FilteringSpec.cxx @@ -38,7 +38,6 @@ #include "Framework/InputRecordWalker.h" #include "Framework/Logger.h" #include "Framework/TableBuilder.h" -#include "Framework/TableTreeHelpers.h" #include "Framework/CCDBParamSpec.h" #include "FDDBase/Constants.h" #include "FT0Base/Geometry.h" @@ -47,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 90964fb1c05fa..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,10 +93,9 @@ 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()); - tpcRefitter->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 } const auto& itsClusters = prepareITSClusters(data); @@ -598,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 e16031f641829..1457790c7c531 100644 --- a/Detectors/GlobalTracking/src/MatchTPCITS.cxx +++ b/Detectors/GlobalTracking/src/MatchTPCITS.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "GPUO2Interface.h" // Needed for propper settings in GPUParam.h +#include "GPUO2ExternalUser.h" // Needed for propper settings in GPUParam.h #include "GPUParam.h" #include "GPUParam.inc" #ifdef WITH_OPENMP @@ -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; } //______________________________________________ @@ -245,8 +246,8 @@ void MatchTPCITS::init() } #endif - if (mParams->runAfterBurner) { // only used in AfterBurner - mRGHelper.init(); // prepare helper for TPC track / ITS clusters matching + if (mParams->runAfterBurner) { // only used in AfterBurner + mRGHelper.init(mParams->lowestLayerAB); // prepare helper for TPC track / ITS clusters matching } clear(); @@ -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,8 +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->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 + 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()) { @@ -708,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--;) { @@ -1754,21 +1755,21 @@ bool MatchTPCITS::refitABTrack(int iITSAB, const TPCABSeed& seed, pmr::vectorestimateLTFast(tofL, winLink); // guess about initial value for the track integral from the origin // refit track outward in the ITS const auto& itsClRefs = ABTrackletRefs[iITSAB]; int nclRefit = 0, ncl = itsClRefs.getNClusters(); - float chi2 = 0.f; // NOTE: the ITS cluster absolute indices are stored from inner to outer layers for (int icl = itsClRefs.getFirstEntry(); icl < itsClRefs.getEntriesBound(); icl++) { const auto& clus = mITSClustersArray[ABTrackletClusterIDs[icl]]; float alpha = geom->getSensorRefAlpha(clus.getSensorID()), x = clus.getX(); - if (!tracOut.rotate(alpha) || + if (!tracOut.rotate(alpha, refLin, propagator->getNominalBz()) || // note: here we also calculate the L,T integral // note: we should eventually use TPC pid in the refit (TODO) // note: since we are at small R, we can use field BZ component at origin rather than 3D field - !propagator->propagateToX(tracOut, x, propagator->getNominalBz(), MaxSnp, maxStep, mUseMatCorrFlag, &tofL)) { + !propagator->propagateToX(tracOut, refLin, x, propagator->getNominalBz(), MaxSnp, maxStep, mUseMatCorrFlag, &tofL)) { break; } chi2 += tracOut.getPredictedChi2(clus); @@ -1789,7 +1790,7 @@ bool MatchTPCITS::refitABTrack(int iITSAB, const TPCABSeed& seed, pmr::vectorPropagateToXBxByBz(tracOut, xtogo, MaxSnp, 10., mUseMatCorrFlag, &tofL)) { + !propagator->PropagateToXBxByBz(tracOut, refLin, xtogo, MaxSnp, 10., mUseMatCorrFlag, &tofL)) { LOG(debug) << "Propagation to inner TPC boundary X=" << xtogo << " failed, Xtr=" << tracOut.getX() << " snp=" << tracOut.getSnp(); matchedTracks.pop_back(); // destroy failed track return false; 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 8a7611e3380a4..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,11 +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); - } + 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; @@ -78,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; @@ -91,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); // } @@ -114,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; @@ -131,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, @@ -144,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()); } } @@ -159,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); @@ -176,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{ @@ -202,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 d71a4fad7ab78..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" @@ -197,6 +197,14 @@ void PrimaryVertexingSpec::run(ProcessingContext& pc) mVertexer.getTimeReAttach().CpuTime(), mVertexer.getTotTrials(), mVertexer.getNTZClusters(), mVertexer.getMaxTrialsPerCluster(), mVertexer.getLongestClusterTimeMS(), mVertexer.getLongestClusterMult(), mVertexer.getNIniFound(), mVertexer.getNKilledBCValid(), mVertexer.getNKilledIntCand(), mVertexer.getNKilledDebris(), mVertexer.getNKilledQuality(), mVertexer.getNKilledITSOnly()); + + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, PVertexerParams::Instance().getName()), PVertexerParams::Instance().getName()); + } + } } void PrimaryVertexingSpec::endOfStream(EndOfStreamContext& ec) diff --git a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx index 80ba5f94280a0..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,11 +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) - { - mTPCCorrMapsLoader.setLumiScaleType(sclOpts.lumiType); - mTPCCorrMapsLoader.setLumiScaleMode(sclOpts.lumiMode); - } + 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; @@ -74,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; @@ -104,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) @@ -127,6 +119,17 @@ void SecondaryVertexingSpec::run(ProcessingContext& pc) mVertexer.getNV0s(), calls[0] - fitCalls[0], mVertexer.getNCascades(), calls[1] - fitCalls[1], mVertexer.getN3Bodies(), calls[2] - fitCalls[2], mVertexer.getNStrangeTracks(), mTimer.CpuTime() - timeCPU0, mTimer.RealTime() - timeReal0); fitCalls = calls; + + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, SVertexerParams::Instance().getName()), SVertexerParams::Instance().getName()); + if (mEnableStrangenessTracking) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()), o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()); + } + } + } } void SecondaryVertexingSpec::endOfStream(EndOfStreamContext& ec) @@ -143,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); @@ -179,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 @@ -209,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, @@ -222,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) { @@ -239,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{ @@ -254,7 +247,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas src |= (srcClus = GTrackID::getSourceMask(GTrackID::ITS)); } if (GTrackID::includesDet(o2::detectors::DetID::TPC, src) && !src[GTrackID::TPC]) { - throw std::runtime_error("Tracks involving TPC were requested w/o requesting TPC-only tracks"); + LOGP(warn, "Tracks involving TPC were requested w/o requesting TPC-only tracks, simplified selection will be applied"); } if (src[GTrackID::TPC]) { srcClus |= GTrackID::getSourceMask(GTrackID::TPC); @@ -283,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 @@ -310,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/StrangenessTrackingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx index 849964aeaf871..e313940b0a91e 100644 --- a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx @@ -17,10 +17,8 @@ #include "DataFormatsGlobalTracking/RecoContainer.h" #include "StrangenessTracking/StrangenessTrackingConfigParam.h" #include "GlobalTrackingWorkflow/StrangenessTrackingSpec.h" -#include "ITSWorkflow/ClusterWriterSpec.h" #include "ITSWorkflow/TrackerSpec.h" #include "ITSWorkflow/TrackReaderSpec.h" -#include "ITSMFTWorkflow/ClusterReaderSpec.h" #include "Framework/CCDBParamSpec.h" #include "DataFormatsParameters/GRPObject.h" diff --git a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx index 4710302e4e91e..746e572c506b8 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx @@ -22,8 +22,9 @@ #include "DataFormatsGlobalTracking/RecoContainer.h" #include "Framework/Task.h" #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" @@ -58,11 +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); - } + 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; @@ -74,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; @@ -82,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; }; @@ -94,7 +92,6 @@ void TOFMatcherSpec::init(InitContext& ic) if (mStrict) { mMatcher.setHighPurity(); } - mTPCCorrMapsLoader.init(ic); mMatcher.storeMatchable(mPushMatchable); mMatcher.setExtraTimeToleranceTRD(mExtraTolTRD); mMatcher.setNlanes(mNlanes); @@ -104,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(); @@ -114,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, @@ -128,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()); } } @@ -143,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) @@ -228,6 +214,14 @@ void TOFMatcherSpec::run(ProcessingContext& pc) pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "MATCHABLES_17", 0}, mMatcher.getMatchedTracksPair(17)); } + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTOFParams::Instance().getName()), MatchTOFParams::Instance().getName()); + } + } + mTimer.Stop(); } @@ -237,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; @@ -263,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); @@ -317,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 1368bf6f34fe4..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,13 +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); - } + 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; @@ -87,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; }; @@ -107,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) @@ -129,6 +125,14 @@ void TPCITSMatchingDPL::run(ProcessingContext& pc) mMatching.run(recoData, matchedTracks, ABTrackletRefs, ABTrackletClusterIDs, matchLabels, ABTrackletLabels, calib); + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTPCITSParams::Instance().getName()), MatchTPCITSParams::Instance().getName()); + } + } + mTimer.Stop(); } @@ -148,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; @@ -184,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; @@ -218,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(); @@ -296,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 776d3946283c3..4ee9f2f314a08 100644 --- a/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt +++ b/Detectors/GlobalTrackingWorkflow/study/CMakeLists.txt @@ -25,6 +25,9 @@ o2_add_library(GlobalTrackingStudy src/TrackMCStudyConfig.cxx src/TrackMCStudyTypes.cxx src/TPCClusSelector.cxx + src/CheckResidSpec.cxx + src/CheckResidConfig.cxx + src/HistoManager.cxx PUBLIC_LINK_LIBRARIES O2::GlobalTracking O2::GlobalTrackingWorkflowReaders O2::GlobalTrackingWorkflowHelpers @@ -38,6 +41,9 @@ o2_target_root_dictionary(GlobalTrackingStudy include/GlobalTrackingStudy/TrackInfoExt.h include/GlobalTrackingStudy/TrackMCStudyConfig.h include/GlobalTrackingStudy/TrackMCStudyTypes.h + include/GlobalTrackingStudy/CheckResidTypes.h + include/GlobalTrackingStudy/CheckResidConfig.h + include/GlobalTrackingStudy/HistoManager.h LINKDEF src/GlobalTrackingStudyLinkDef.h ) @@ -76,6 +82,11 @@ o2_add_executable(dump-workfow SOURCES src/track-dump-workflow.cxx PUBLIC_LINK_LIBRARIES O2::GlobalTrackingStudy) +o2_add_executable(resid-workfow + COMPONENT_NAME check + SOURCES src/check-resid-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::GlobalTrackingStudy) + if (OpenMP_CXX_FOUND) target_compile_definitions(${targetName} PRIVATE WITH_OPENMP) target_link_libraries(${targetName} PRIVATE OpenMP::OpenMP_CXX) diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h new file mode 100644 index 0000000000000..09ebba2d2e3f2 --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidConfig.h @@ -0,0 +1,63 @@ +// 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_CHECK_RESID_CONFIG_H +#define O2_CHECK_RESID_CONFIG_H +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +namespace o2::checkresid +{ +struct CheckResidConfig : o2::conf::ConfigurableParamHelper { + int minPVContributors = 10; + int minTPCCl = 60; + int minITSCl = 7; + float minPt = 0.4f; + float maxPt = 50.f; + float maxTgl = 2.f; + float rCompIBOB = 12.f; + + bool pvcontribOnly = true; + bool addPVAsCluster = true; + bool useStableRef = true; + bool doIBOB = true; + bool doResid = true; + + bool refitPV = true; + 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 + +#endif diff --git a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/DecodedDataDumpSpec.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h similarity index 58% rename from Detectors/MUON/MID/Workflow/include/MIDWorkflow/DecodedDataDumpSpec.h rename to Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h index 4d104aacac15c..3cae8e94b8e68 100644 --- a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/DecodedDataDumpSpec.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidSpec.h @@ -9,22 +9,18 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file MIDWorkflow/RawDumpSpec.h -/// \brief Device to dump decoded raw data -/// \author Diego Stocco -/// \date 17 February 2022 - -#ifndef O2_MID_RAWDUMPSPEC_H -#define O2_MID_RAWDUMPSPEC_H +#ifndef O2_CHECK_RESID_H +#define O2_CHECK_RESID_H +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "Framework/Task.h" #include "Framework/DataProcessorSpec.h" -namespace o2 -{ -namespace mid +namespace o2::checkresid { -framework::DataProcessorSpec getRawDumpSpec(); -} // namespace mid -} // namespace o2 +/// create a processor spec +o2::framework::DataProcessorSpec getCheckResidSpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool drawOnly, bool postProcOnly); + +} // namespace o2::checkresid -#endif // O2_MID_RAWDUMPSPEC_H +#endif // O2_CHECK_RESID_H diff --git a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidTypes.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidTypes.h new file mode 100644 index 0000000000000..ebb6a7aabe9fa --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/CheckResidTypes.h @@ -0,0 +1,42 @@ +// 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_CHECK_RESID_TYPES_H +#define O2_CHECK_RESID_TYPES_H + +#include "ReconstructionDataFormats/Track.h" + +namespace o2::checkresid +{ +struct Point { + float dy = 0.f; + float dz = 0.f; + float sig2y = 0.f; + float sig2z = 0.f; + float phi = 0.f; + float z = 0.f; + int16_t sens = -1; + int8_t lr = -1; // -1 = vtx + ClassDefNV(Point, 1) +}; + +struct Track { + o2::dataformats::GlobalTrackID gid{}; + o2::track::TrackPar track; + o2::track::TrackParCov trIBOut; + o2::track::TrackParCov trOBInw; + std::vector points; + ClassDefNV(Track, 1) +}; + +} // namespace o2::checkresid + +#endif 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/TrackMCStudyConfig.h b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudyConfig.h index 74d77eb3d53de..ed78ba2a710ec 100644 --- a/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudyConfig.h +++ b/Detectors/GlobalTrackingWorkflow/study/include/GlobalTrackingStudy/TrackMCStudyConfig.h @@ -27,11 +27,14 @@ struct TrackMCStudyConfig : o2::conf::ConfigurableParamHelper #include @@ -33,6 +34,7 @@ struct MCTrackInfo { int getLowestITSLayer() const; int getHighestITSLayer() const; std::vector occTPCV{}; + std::vector trackRefsTPC{}; o2::track::TrackPar track{}; o2::MCCompLabel label{}; float occTPC = -1.f; @@ -73,7 +75,7 @@ struct MCTrackInfo { float getTrackParTPCPar(int i, float b, float x = 90) const; float getTrackParTPCPhiSec(float b, float x = 90) const; - ClassDefNV(MCTrackInfo, 7); + ClassDefNV(MCTrackInfo, 8); }; struct RecTrack { @@ -288,6 +290,16 @@ struct ClResTPC { ClassDefNV(ClResTPC, 2); }; +struct ITSHitInfo { + o2::BaseCluster clus{}; + o2::TrackReference tref{}; + float trefXT = 0; // track ref tracking frame coordinates + float trefYT = 0; + float chipX = 0; + float chipAlpha = 0; + ClassDefNV(ITSHitInfo, 1); +}; + struct RecPV { o2::dataformats::PrimaryVertex pv{}; o2::MCEventLabel mcEvLbl{}; 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/CheckResidConfig.cxx b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidConfig.cxx new file mode 100644 index 0000000000000..a754d1196017f --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/src/CheckResidConfig.cxx @@ -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. + +#include "GlobalTrackingStudy/CheckResidConfig.h" + +O2ParamImpl(o2::checkresid::CheckResidConfig); 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 dbf34b8eb14ad..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" @@ -48,7 +48,7 @@ using TBracket = o2::math_utils::Bracketf_t; using timeEst = o2::dataformats::TimeStampWithError; -class DumpTracksSpec : public Task +class DumpTracksSpec final : public Task { public: DumpTracksSpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useMC) diff --git a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h index f0d3e7d4d0b4e..b1fe732fbefc4 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h +++ b/Detectors/GlobalTrackingWorkflow/study/src/GlobalTrackingStudyLinkDef.h @@ -40,4 +40,15 @@ #pragma link C++ class std::vector < o2::trackstudy::TrackPairInfo> + ; #pragma ling C++ class o2::tpc::TPCClusSelector + ; +#pragma link C++ class o2::trackstudy::ITSHitInfo + ; +#pragma link C++ class std::vector < o2::trackstudy::ITSHitInfo> + ; + +#pragma link C++ class o2::checkresid::Point + ; +#pragma link C++ class std::vector < o2::checkresid::Point> + ; +#pragma link C++ class o2::checkresid::Track + ; +#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 1e141a29d3f55..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" @@ -68,7 +68,7 @@ using V0ID = o2::dataformats::V0Index; using timeEst = o2::dataformats::TimeStampWithError; -class SVStudySpec : public Task +class SVStudySpec final : public Task { public: SVStudySpec(std::shared_ptr dr, std::shared_ptr gr, GTrackID::mask_t src, bool useTPCCl, bool useMC) diff --git a/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx b/Detectors/GlobalTrackingWorkflow/study/src/TPCTrackStudy.cxx index 1cb108da5a460..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" @@ -47,15 +47,11 @@ using TBracket = o2::math_utils::Bracketf_t; using timeEst = o2::dataformats::TimeStampWithError; -class TPCTrackStudySpec : public Task +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); - } + 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; @@ -68,7 +64,7 @@ class TPCTrackStudySpec : 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.; @@ -107,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"); @@ -138,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) @@ -186,8 +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->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 + 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 9f4b79ab47b72..9637c72589196 100644 --- a/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx +++ b/Detectors/GlobalTrackingWorkflow/study/src/TrackMCStudy.cxx @@ -17,13 +17,15 @@ #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" #include "DetectorsBase/GeometryManager.h" +#include "ITSBase/GeometryTGeo.h" #include "SimulationDataFormat/MCEventLabel.h" #include "SimulationDataFormat/MCUtils.h" #include "SimulationDataFormat/O2DatabasePDG.h" +#include "SimulationDataFormat/TrackReference.h" #include "CommonDataFormat/BunchFilling.h" #include "CommonUtils/NameConf.h" #include "DataFormatsFT0/RecPoints.h" @@ -31,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" @@ -54,6 +56,7 @@ #include "GPUParam.h" #include "GPUParam.inc" #include "MathUtils/fit.h" +#include "TPCFastTransformPOD.h" #include #include #include @@ -80,15 +83,11 @@ using TBracket = o2::math_utils::Bracketf_t; using timeEst = o2::dataformats::TimeStampWithError; -class TrackMCStudy : public Task +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); - } + 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; @@ -98,6 +97,7 @@ class TrackMCStudy : public Task private: void processTPCTrackRefs(); + void processITSTracks(const o2::globaltracking::RecoContainer& recoData); void loadTPCOccMap(const o2::globaltracking::RecoContainer& recoData); void fillMCClusterInfo(const o2::globaltracking::RecoContainer& recoData); void prepareITSData(const o2::globaltracking::RecoContainer& recoData); @@ -112,18 +112,21 @@ class TrackMCStudy : 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 - 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 + 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 float mNTPCOccBinLengthInv = -1.f; int mVerbose = 0; float mITSTimeBiasMUS = 0.f; @@ -171,7 +174,6 @@ void TrackMCStudy::init(InitContext& ic) mNCheckDecays++; } mDecaysMaps.resize(mNCheckDecays); - mTPCCorrMapsLoader.init(ic); } void TrackMCStudy::run(ProcessingContext& pc) @@ -194,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; @@ -220,7 +207,7 @@ void TrackMCStudy::updateTimeDependentParams(ProcessingContext& pc) auto& elParam = o2::tpc::ParameterElectronics::Instance(); mTPCTBinMUS = elParam.ZbinWidth; - + o2::its::GeometryTGeo::Instance()->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2GRot) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L)); if (mCheckSV) { const auto& svparam = o2::vertexing::SVertexerParams::Instance(); mFitterV0.setBz(o2::base::Propagator::Instance()->getNominalBz()); @@ -252,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); @@ -506,7 +493,7 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) } LOGP(info, "collected {} MC tracks", mSelMCTracks.size()); - if (params.minTPCRefsToExtractClRes > 0) { // prepare MC trackrefs for TPC + if (params.minTPCRefsToExtractClRes > 0 || params.storeTPCTrackRefs) { // prepare MC trackrefs for TPC processTPCTrackRefs(); } @@ -531,6 +518,15 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) } return lhs.gid.getSource() > rhs.gid.getSource(); }); + if (params.storeTPCTrackRefs) { + auto rft = mSelTRefIdx.find(entry.first); + if (rft != mSelTRefIdx.end()) { + auto rfent = rft->second; + for (int irf = rfent.first; irf < rfent.second; irf++) { + trackFam.mcTrackInfo.trackRefsTPC.push_back(mSelTRefs[irf]); + } + } + } // fill track params int tcnt = 0; for (auto& tref : tracks) { @@ -560,11 +556,30 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) tref.flags |= RecTrack::FakeITS; } } - if (msk[DetID::TPC] && trackFam.entITSTPC < 0) { // has both ITS and TPC contribution - trackFam.entITSTPC = tcnt; + if (msk[DetID::TPC]) { + if (trackFam.entITSTPC < 0) { // has both ITS and TPC contribution + trackFam.entITSTPC = tcnt; + } if (recoData.getTrackMCLabel(gidSet[GTrackID::ITSTPC]).isFake()) { tref.flags |= RecTrack::FakeITSTPC; } + + if (msk[DetID::TRD]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::ITSTPCTRD]).isFake()) { + tref.flags |= RecTrack::FakeTRD; + } + if (msk[DetID::TOF]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::ITSTPCTRDTOF]).isFake()) { + tref.flags |= RecTrack::FakeTOF; + } + } + } else { + if (msk[DetID::TOF]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::ITSTPCTOF]).isFake()) { + tref.flags |= RecTrack::FakeTOF; + } + } + } } } if (msk[DetID::TPC]) { @@ -582,6 +597,24 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) if (recoData.getTrackMCLabel(gidSet[GTrackID::TPC]).isFake()) { tref.flags |= RecTrack::FakeTPC; } + if (!msk[DetID::ITS]) { + if (msk[DetID::TRD]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::TPCTRD]).isFake()) { + tref.flags |= RecTrack::FakeTRD; + } + if (msk[DetID::TOF]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::TPCTRDTOF]).isFake()) { + tref.flags |= RecTrack::FakeTOF; + } + } + } else { + if (msk[DetID::TOF]) { + if (recoData.getTrackMCLabel(gidSet[GTrackID::TPCTOF]).isFake()) { + tref.flags |= RecTrack::FakeTOF; + } + } + } + } } float ts = 0, terr = 0; if (tref.gid.getSource() != GTrackID::ITS) { @@ -597,8 +630,8 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) tcnt++; } if (trackFam.entITS > -1 && trackFam.entTPC > -1) { // ITS and TPC were found but matching failed - auto vidITS = tracks[trackFam.entITS].gid; - auto vidTPC = tracks[trackFam.entTPC].gid; + auto vidITS = recoData.getITSContributorGID(tracks[trackFam.entITS].gid); + auto vidTPC = recoData.getTPCContributorGID(tracks[trackFam.entTPC].gid); auto trcTPC = recoData.getTrackParam(vidTPC); auto trcITS = recoData.getTrackParamOut(vidITS); if (propagateToRefX(trcTPC, trcITS)) { @@ -704,6 +737,10 @@ void TrackMCStudy::process(const o2::globaltracking::RecoContainer& recoData) }); (*mDBGOut) << "mcVtxTree" << "mcVtx=" << mcVtx << "\n"; } + + if (params.storeITSInfo) { + processITSTracks(recoData); + } } void TrackMCStudy::processTPCTrackRefs() @@ -813,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 @@ -965,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(); @@ -976,6 +1010,11 @@ void TrackMCStudy::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) mITSROFrameLengthMUS = par.roFrameLengthInBC * o2::constants::lhc::LHCBunchSpacingNS * 1e-3; return; } + if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { + LOG(info) << "cluster dictionary updated"; + mITSDict = (const o2::itsmft::TopologyDictionary*)obj; + return; + } } //_____________________________________________________ @@ -1023,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; @@ -1201,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(); @@ -1229,7 +1268,86 @@ void TrackMCStudy::loadTPCOccMap(const o2::globaltracking::RecoContainer& recoDa } } -DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, const o2::tpc::CorrectionMapsLoaderGloOpts& sclOpts, bool checkSV) +void TrackMCStudy::processITSTracks(const o2::globaltracking::RecoContainer& recoData) +{ + if (!mITSDict) { + LOGP(warn, "ITS data is not loaded"); + return; + } + const auto itsTracks = recoData.getITSTracks(); + const auto itsLbls = recoData.getITSTracksMCLabels(); + const auto itsClRefs = recoData.getITSTracksClusterRefs(); + const auto clusITS = recoData.getITSClusters(); + const auto patterns = recoData.getITSClustersPatterns(); + const auto& params = o2::trackstudy::TrackMCStudyConfig::Instance(); + auto pattIt = patterns.begin(); + mITSClustersArray.clear(); + mITSClustersArray.reserve(clusITS.size()); + + o2::its::ioutils::convertCompactClusters(clusITS, pattIt, mITSClustersArray, mITSDict); + auto geom = o2::its::GeometryTGeo::Instance(); + int ntr = itsLbls.size(); + LOGP(info, "We have {} ITS clusters and the number of patterns is {}, ITSdict:{} NMCLabels: {}", clusITS.size(), patterns.size(), mITSDict != nullptr, itsLbls.size()); + + std::vector evord(ntr); + std::iota(evord.begin(), evord.end(), 0); + std::sort(evord.begin(), evord.end(), [&](int i, int j) { return itsLbls[i] < itsLbls[j]; }); + std::vector outHitInfo; + std::array cl2arr{}; + + for (int itr0 = 0; itr0 < ntr; itr0++) { + auto itr = evord[itr0]; + const auto& itsTr = itsTracks[itr]; + const auto& itsLb = itsLbls[itr]; + // LOGP(info,"proc {} {} {}",itr0, itr, itsLb.asString()); + int nCl = itsTr.getNClusters(); + if (itsLb.isFake() || nCl < params.minITSClForITSoutput) { + continue; + } + auto entrySel = mSelMCTracks.find(itsLb); + if (entrySel == mSelMCTracks.end()) { + continue; + } + outHitInfo.clear(); + cl2arr.fill(-1); + auto clEntry = itsTr.getFirstClusterEntry(); + for (int iCl = nCl; iCl--;) { // clusters are stored from outer to inner layers + const auto& cls = mITSClustersArray[itsClRefs[clEntry + iCl]]; + int hpos = outHitInfo.size(); + auto& hinf = outHitInfo.emplace_back(); + hinf.clus = cls; + hinf.clus.setCount(geom->getLayer(cls.getSensorID())); + geom->getSensorXAlphaRefPlane(cls.getSensorID(), hinf.chipX, hinf.chipAlpha); + cl2arr[hinf.clus.getCount()] = hpos; // to facilitate finding the cluster of the layer + } + auto trspan = mcReader.getTrackRefs(itsLb.getSourceID(), itsLb.getEventID(), itsLb.getTrackID()); + int ilrc = -1, nrefAcc = 0; + for (const auto& trf : trspan) { + if (trf.getDetectorId() != 0) { // process ITS only + continue; + } + int lrt = trf.getUserId(); // layer of the reference, but there might be multiple hits on the same layer + int clEnt = cl2arr[lrt]; + if (clEnt < 0) { + continue; + } + auto& hinf = outHitInfo[clEnt]; + float traX, traY; + o2::math_utils::rotateZInv(trf.X(), trf.Y(), traX, traY, std::sin(hinf.chipAlpha), std::cos(hinf.chipAlpha)); // tracking coordinates of the reference + if (hinf.trefXT < 1 || std::abs(traX - hinf.chipX) < std::abs(hinf.trefXT - hinf.chipX)) { + if (hinf.trefXT < 1) { + nrefAcc++; + } + hinf.tref = trf; + hinf.trefXT = traX; + hinf.trefYT = traY; + } + } + (*mDBGOut) << "itsTree" << "hits=" << outHitInfo << "trIn=" << ((o2::track::TrackParCov&)itsTr) << "trOut=" << itsTr.getParamOut() << "mcTr=" << entrySel->second.mcTrackInfo.track << "mcPDG=" << entrySel->second.mcTrackInfo.pdg << "nTrefs=" << nrefAcc << "\n"; + } +} + +DataProcessorSpec getTrackMCStudySpec(GTrackID::mask_t srcTracks, GTrackID::mask_t srcClusters, bool checkSV) { std::vector outputs; Options opts{ @@ -1248,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 @@ -1262,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 d9ea9fe4516e1..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,14 +42,14 @@ #include "ReconstructionDataFormats/VtxTrackRef.h" #include "ReconstructionDataFormats/DCA.h" #include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" #include "GPUO2InterfaceRefit.h" -#include "GPUO2Interface.h" // Needed for propper settings in GPUParam.h +#include "GPUO2ExternalUser.h" // Needed for propper settings in GPUParam.h #include "GPUParam.h" #include "GPUParam.inc" #include "GPUTPCGeometry.h" #include "Steer/MCKinematicsReader.h" #include "MathUtils/fit.h" +#include "TPCFastTransformPOD.h" #include namespace o2::trackstudy @@ -67,15 +67,11 @@ using TBracket = o2::math_utils::Bracketf_t; using timeEst = o2::dataformats::TimeStampWithError; -class TrackingStudySpec : public Task +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); - } + 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; @@ -90,11 +86,11 @@ class TrackingStudySpec : 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; @@ -124,7 +120,6 @@ class TrackingStudySpec : 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); @@ -159,10 +154,9 @@ 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()); - mTPCRefitter->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 mNTPCOccBinLength = mTPCRefitter->getParam()->rec.tpc.occupancyMapTimeBins; mTBinClOccBef.clear(); mTBinClOccAft.clear(); @@ -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; } @@ -443,7 +422,7 @@ void TrackingStudySpec::process(o2::globaltracking::RecoContainer& recoData) } bool ambig = vid.isAmbiguous(); auto trc = recoData.getTrackParam(vid); - if (abs(trc.getEta()) > mMaxEta) { + if (fabs(trc.getEta()) > mMaxEta) { continue; } if (iv < nv - 1 && is == GTrackID::TPC && tpcTr && !tpcTr->hasBothSidesClusters()) { // for unconstrained TPC tracks correct track Z @@ -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 new file mode 100644 index 0000000000000..0791d72474ad3 --- /dev/null +++ b/Detectors/GlobalTrackingWorkflow/study/src/check-resid-workflow.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 "GlobalTrackingStudy/CheckResidSpec.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "Framework/CallbacksPolicy.h" +#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; +using DetID = o2::detectors::DetID; + +// ------------------------------------------------------------------ +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + // option allowing to set parameters + std::vector options{ + {"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::itsmft::DPLAlpideParamInitializer::addITSConfigOption(options); + o2::raw::HBFUtilsInitializer::addConfigOption(options); + std::swap(workflowOptions, options); +} + +// ------------------------------------------------------------------ + +#include "Framework/runDataProcessing.h" + +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")); + + 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 (!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 + 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/tofworkflow/src/RecoWorkflowSpec.cxx b/Detectors/GlobalTrackingWorkflow/tofworkflow/src/RecoWorkflowSpec.cxx deleted file mode 100644 index ab4f90464b31b..0000000000000 --- a/Detectors/GlobalTrackingWorkflow/tofworkflow/src/RecoWorkflowSpec.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. - -#include "TOFWorkflow/RecoWorkflowSpec.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/DataRefUtils.h" -#include "Framework/Lifetime.h" -#include "Framework/Task.h" -#include "Framework/SerializationMethods.h" -#include "Headers/DataHeader.h" -#include "DataFormatsTOF/Cluster.h" -#include "GlobalTracking/MatchTOF.h" -#include "ReconstructionDataFormats/TrackTPCITS.h" -#include "DetectorsBase/GeometryManager.h" -#include "DetectorsBase/Propagator.h" -#include "DetectorsBase/GRPGeomHelper.h" -#include "CommonUtils/NameConf.h" -#include -#include "TStopwatch.h" -#include "TPCCalibration/VDriftHelper.h" - -// from FIT -#include "DataFormatsFT0/RecPoints.h" - -#include // for make_shared, make_unique, unique_ptr -#include - -using namespace o2::framework; - -namespace o2 -{ -namespace tof -{ - -// use the tasking system of DPL -// just need to implement 2 special methods init + run (there is no need to inherit from anything) -class TOFDPLRecoWorkflowTask -{ - using evIdx = o2::dataformats::EvIndex; - using MatchOutputType = std::vector; - - bool mUseMC = true; - bool mUseFIT = false; - - public: - explicit TOFDPLRecoWorkflowTask(std::shared_ptr gr, bool useMC, bool useFIT) : mGGCCDBRequest(gr), mUseMC(useMC), mUseFIT(useFIT) {} - - void init(framework::InitContext& ic) - { - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - mTimer.Stop(); - mTimer.Reset(); - } - - void run(framework::ProcessingContext& pc) - { - mTimer.Start(false); - updateTimeDependentParams(pc); - //>>>---------- attach input data --------------->>> - const auto clustersRO = pc.inputs().get>("tofcluster"); - const auto tracksRO = pc.inputs().get>("globaltrack"); - - if (mUseFIT) { - // Note: the particular variable will go out of scope, but the span is passed by copy to the - // worker and the underlying memory is valid throughout the whole computation - auto recPoints = std::move(pc.inputs().get>("fitrecpoints")); - mMatcher.setFITRecPoints(recPoints); - LOG(info) << "TOF Reco Workflow pulled " << recPoints.size() << " FIT RecPoints"; - } - - // we do a copy of the input but we are looking for a way to avoid it (current problem in conversion form unique_ptr to *) - - gsl::span itstpclab; - o2::dataformats::MCTruthContainer toflab; - if (mUseMC) { - const auto toflabel = pc.inputs().get*>("tofclusterlabel"); - itstpclab = pc.inputs().get>("itstpclabel"); - toflab = std::move(*toflabel); - } - - mMatcher.run(tracksRO, clustersRO, toflab, itstpclab); - - // in run_match_tof aggiugnere esplicitamente la chiamata a fill del tree (nella classe MatchTOF) e il metodo per leggere i vettori di output - - //... - // LOG(info) << "TOF CLUSTERER : TRANSFORMED " << digits->size() - // << " DIGITS TO " << mClustersArray.size() << " CLUSTERS"; - - // send matching-info - pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "MTC_ITSTPC", 0}, mMatcher.getMatchedTrackVector()); - if (mUseMC) { - pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "MCMATCHTOF", 0}, mMatcher.getMatchedTOFLabelsVector()); - } - pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "CALIBDATA", 0}, mMatcher.getCalibVector()); - mTimer.Stop(); - } - - void endOfStream(EndOfStreamContext& ec) - { - LOGF(info, "TOF Matching total timing: Cpu: %.3e Real: %.3e s in %d slots", - mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); - } - - void updateTimeDependentParams(ProcessingContext& pc) - { - o2::base::GRPGeomHelper::instance().checkUpdates(pc); - mTPCVDriftHelper.extractCCDBInputs(pc); - static bool initOnceDone = false; - if (!initOnceDone) { // this params need to be queried only once - initOnceDone = true; - // put here init-once stuff - } - // we may have other params which need to be queried regularly - if (mTPCVDriftHelper.isUpdated()) { - LOGP(info, "Updating TPC fast transform map with new VDrift factor of {} wrt reference {} from source {}", - mTPCVDriftHelper.getVDriftObject().corrFact, mTPCVDriftHelper.getVDriftObject().refVDrift, mTPCVDriftHelper.getSourceName()); - mMatcher.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); - mTPCVDriftHelper.acknowledgeUpdate(); - } - } - - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) - { - if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { - return; - } - if (mTPCVDriftHelper.accountCCDBInputs(matcher, obj)) { - return; - } - } - - private: - o2::globaltracking::MatchTOF mMatcher; ///< Cluster finder - std::shared_ptr mGGCCDBRequest; - o2::tpc::VDriftHelper mTPCVDriftHelper{}; - TStopwatch mTimer; -}; - -o2::framework::DataProcessorSpec getTOFRecoWorkflowSpec(bool useMC, bool useFIT) -{ - std::vector inputs; - std::vector outputs; - inputs.emplace_back("tofcluster", o2::header::gDataOriginTOF, "CLUSTERS", 0, Lifetime::Timeframe); - inputs.emplace_back("globaltrack", "GLO", "TPCITS", 0, Lifetime::Timeframe); - if (useMC) { - inputs.emplace_back("tofclusterlabel", o2::header::gDataOriginTOF, "CLUSTERSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("itstpclabel", "GLO", "TPCITS_MC", 0, Lifetime::Timeframe); - } - - if (useFIT) { - inputs.emplace_back("fitrecpoints", o2::header::gDataOriginFT0, "RECPOINTS", 0, Lifetime::Timeframe); - } - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - false, // GRPLHCIF - true, // GRPMagField - true, // askMatLUT - o2::base::GRPGeomRequest::Aligned, // geometry - inputs, - true); - o2::tpc::VDriftHelper::requestCCDBInputs(inputs); - - outputs.emplace_back(o2::header::gDataOriginTOF, "MTC_ITSTPC", 0, Lifetime::Timeframe); - if (useMC) { - outputs.emplace_back(o2::header::gDataOriginTOF, "MCMATCHTOF", 0, Lifetime::Timeframe); - } - outputs.emplace_back(o2::header::gDataOriginTOF, "CALIBDATA", 0, Lifetime::Timeframe); - - return DataProcessorSpec{ - "TOFRecoWorkflow", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, useMC, useFIT)}, - Options{ - {"material-lut-path", VariantType::String, "", {"Path of the material LUT file"}}}}; -} - -} // end namespace tof -} // end namespace o2 diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/CMakeLists.txt b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/CMakeLists.txt index c8db0209d4471..09ec6081b06b8 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/CMakeLists.txt +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/CMakeLists.txt @@ -9,6 +9,8 @@ # 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(TPCInterpolationWorkflow SOURCES src/TPCInterpolationSpec.cxx src/TPCResidualWriterSpec.cxx diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCResidualAggregatorSpec.h b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCResidualAggregatorSpec.h index b9c99f9e65676..99f20e390a09a 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCResidualAggregatorSpec.h +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCResidualAggregatorSpec.h @@ -128,8 +128,9 @@ class ResidualAggregatorDevice : public o2::framework::Task updateTimeDependentParams(pc); std::chrono::duration ccdbUpdateTime = std::chrono::high_resolution_clock::now() - runStartTime; - // we always require the unbinned residuals and the associated track references + // we always require the unbinned residuals and the associated detector info and track references auto residualsData = pc.inputs().get>("unbinnedRes"); + auto residualsDataDet = pc.inputs().get>("detinfoRes"); auto trackRefs = pc.inputs().get>("trackRefs"); // track data input is optional @@ -151,7 +152,7 @@ class ResidualAggregatorDevice : public o2::framework::Task o2::base::TFIDInfoHelper::fillTFIDInfo(pc, mAggregator->getCurrentTFInfo()); LOG(detail) << "Processing TF " << mAggregator->getCurrentTFInfo().tfCounter << " with " << trkData->size() << " tracks and " << residualsData.size() << " unbinned residuals associated to them"; - mAggregator->process(residualsData, trackRefs, trkDataPtr, lumi); + mAggregator->process(residualsData, residualsDataDet, trackRefs, trkDataPtr, lumi); std::chrono::duration runDuration = std::chrono::high_resolution_clock::now() - runStartTime; LOGP(debug, "Duration for run method: {} ms. From this taken for time dependent param update: {} ms", std::chrono::duration_cast(runDuration).count(), @@ -222,6 +223,7 @@ DataProcessorSpec getTPCResidualAggregatorSpec(bool trackInput, bool ctpInput, b auto& inputs = dataRequest->inputs; o2::tpc::VDriftHelper::requestCCDBInputs(inputs); inputs.emplace_back("unbinnedRes", "GLO", "UNBINNEDRES"); + inputs.emplace_back("detinfoRes", "GLO", "DETINFORES"); inputs.emplace_back("trackRefs", "GLO", "TRKREFS"); if (trackInput) { inputs.emplace_back("trkData", "GLO", "TRKDATA"); diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCUnbinnedResidualReaderSpec.h b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCUnbinnedResidualReaderSpec.h index 6c40bb355eb21..724151c90576f 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCUnbinnedResidualReaderSpec.h +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/include/TPCInterpolationWorkflow/TPCUnbinnedResidualReaderSpec.h @@ -43,6 +43,7 @@ class TPCUnbinnedResidualReader : public o2::framework::Task std::string mInFileName; std::string mInTreeName; std::vector mUnbinnedResid, *mUnbinnedResidPtr = &mUnbinnedResid; + std::vector mDetInfoUnbRes, *mDetInfoUnbResPtr = &mDetInfoUnbRes; std::vector mTrackData, *mTrackDataPtr = &mTrackData; std::vector mTrackDataCompact, *mTrackDataCompactPtr = &mTrackDataCompact; }; diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx index da2fcaab913d7..4f17aec3d49d5 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCInterpolationSpec.cxx @@ -66,11 +66,12 @@ void TPCInterpolationDPL::updateTimeDependentParams(ProcessingContext& pc) initOnceDone = true; // other init-once stuff const auto& param = SpacePointsCalibConfParam::Instance(); + mInterpolation.setSqrtS(o2::base::GRPGeomHelper::instance().getGRPLHCIF()->getSqrtS()); + mInterpolation.setNHBPerTF(o2::base::GRPGeomHelper::getNHBFPerTF()); mInterpolation.init(mSources, mSourcesMap); if (mProcessITSTPConly) { mInterpolation.setProcessITSTPConly(); } - mInterpolation.setSqrtS(o2::base::GRPGeomHelper::instance().getGRPLHCIF()->getSqrtS()); int nTfs = mSlotLength / (o2::base::GRPGeomHelper::getNHBFPerTF() * o2::constants::lhc::LHCOrbitMUS * 1e-6); bool limitTracks = (param.maxTracksPerCalibSlot < 0) ? false : true; int nTracksPerTfMax = (nTfs > 0 && limitTracks) ? param.maxTracksPerCalibSlot / nTfs : -1; @@ -93,6 +94,11 @@ void TPCInterpolationDPL::updateTimeDependentParams(ProcessingContext& pc) mInterpolation.setProcessSeeds(); } o2::its::GeometryTGeo::Instance()->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2GRot) | o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L)); + mInterpolation.setExtDetResid(mExtDetResid); + mInterpolation.setITSClusterDictionary(mITSDict); + if (mDebugOutput) { + mInterpolation.setDumpTrackPoints(); + } } // we may have other params which need to be queried regularly if (mTPCVDriftHelper.isUpdated()) { @@ -103,11 +109,6 @@ void TPCInterpolationDPL::updateTimeDependentParams(ProcessingContext& pc) mInterpolation.setTPCVDrift(mTPCVDriftHelper.getVDriftObject()); mTPCVDriftHelper.acknowledgeUpdate(); } - if (mDebugOutput) { - mInterpolation.setDumpTrackPoints(); - } - mInterpolation.setExtDetResid(mExtDetResid); - mInterpolation.setITSClusterDictionary(mITSDict); } void TPCInterpolationDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) @@ -143,6 +144,7 @@ void TPCInterpolationDPL::run(ProcessingContext& pc) } } pc.outputs().snapshot(Output{"GLO", "UNBINNEDRES", 0}, mInterpolation.getClusterResiduals()); + pc.outputs().snapshot(Output{"GLO", "DETINFORES", 0}, mInterpolation.getClusterResidualsDetInfo()); pc.outputs().snapshot(Output{"GLO", "TRKREFS", 0}, mInterpolation.getTrackDataCompact()); if (mSendTrackData) { pc.outputs().snapshot(Output{"GLO", "TRKDATA", 0}, mInterpolation.getReferenceTracks()); @@ -188,6 +190,7 @@ DataProcessorSpec getTPCInterpolationSpec(GTrackID::mask_t srcCls, GTrackID::mas } } outputs.emplace_back("GLO", "UNBINNEDRES", 0, Lifetime::Timeframe); + outputs.emplace_back("GLO", "DETINFORES", 0, Lifetime::Timeframe); outputs.emplace_back("GLO", "TRKREFS", 0, Lifetime::Timeframe); if (sendTrackData) { outputs.emplace_back("GLO", "TRKDATA", 0, Lifetime::Timeframe); @@ -203,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/TPCResidualWriterSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCResidualWriterSpec.cxx index 5f6d7ad7b361c..8b06444bdb9b3 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCResidualWriterSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCResidualWriterSpec.cxx @@ -38,6 +38,7 @@ DataProcessorSpec getTPCResidualWriterSpec(bool writeTrackData, bool debugOutput BranchDefinition>{InputSpec{"tracksUnfiltered", "GLO", "TPCINT_TRK", 0}, "tracksUnfiltered", ((writeUnfiltered && writeTrackData) ? 1 : 0)}, BranchDefinition>{InputSpec{"residualsUnfiltered", "GLO", "TPCINT_RES", 0}, "residualsUnfiltered", (writeUnfiltered ? 1 : 0)}, BranchDefinition>{InputSpec{"residuals", "GLO", "UNBINNEDRES"}, "residuals"}, + BranchDefinition>{InputSpec{"detInfo", "GLO", "DETINFORES"}, "detInfo"}, BranchDefinition>{InputSpec{"trackRefs", "GLO", "TRKREFS"}, "trackRefs"}, BranchDefinition>{InputSpec{"tracks", "GLO", "TRKDATA"}, "tracks", (writeTrackData ? 1 : 0)}, BranchDefinition>{InputSpec{"trackExt", "GLO", "TRKDATAEXT"}, "trackExt", (debugOutput ? 1 : 0)})(); diff --git a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCUnbinnedResidualReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCUnbinnedResidualReaderSpec.cxx index 55da5a5e71e44..c2dae375731a4 100644 --- a/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCUnbinnedResidualReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/tpcinterpolationworkflow/src/TPCUnbinnedResidualReaderSpec.cxx @@ -44,6 +44,11 @@ void TPCUnbinnedResidualReader::connectTree() assert(mTreeIn); mTreeIn->SetBranchAddress("residuals", &mUnbinnedResidPtr); mTreeIn->SetBranchAddress("trackRefs", &mTrackDataCompactPtr); + if (mTreeIn->GetBranch("detInfo")) { + mTreeIn->SetBranchAddress("detInfo", &mDetInfoUnbResPtr); + } else { + LOGP(warn, "No detInfo branch found in the unbinned residuals tree, empty vector will be sent"); + } if (mTrackInput) { mTreeIn->SetBranchAddress("tracks", &mTrackDataPtr); } @@ -58,6 +63,7 @@ void TPCUnbinnedResidualReader::run(ProcessingContext& pc) LOG(info) << "Pushing " << mUnbinnedResid.size() << " unbinned residuals at entry " << currEntry; pc.outputs().snapshot(Output{"GLO", "UNBINNEDRES", 0}, mUnbinnedResid); pc.outputs().snapshot(Output{"GLO", "TRKREFS", 0}, mTrackDataCompact); + pc.outputs().snapshot(Output{"GLO", "DETINFORES", 0}, mDetInfoUnbRes); if (mTrackInput) { LOG(info) << "Pushing " << mTrackData.size() << " reference tracks for these residuals"; pc.outputs().snapshot(Output{"GLO", "TRKDATA", 0}, mTrackData); @@ -73,6 +79,7 @@ DataProcessorSpec getUnbinnedTPCResidualsReaderSpec(bool trkInput) { std::vector outputs; outputs.emplace_back("GLO", "UNBINNEDRES", 0, Lifetime::Timeframe); + outputs.emplace_back("GLO", "DETINFORES", 0, Lifetime::Timeframe); outputs.emplace_back("GLO", "TRKREFS", 0, Lifetime::Timeframe); if (trkInput) { outputs.emplace_back("GLO", "TRKDATA", 0, Lifetime::Timeframe); 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/HMPID/reconstruction/include/HMPIDReconstruction/CTFCoder.h b/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/CTFCoder.h index da2461c2759ba..894c11864f061 100644 --- a/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/CTFCoder.h +++ b/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/CTFCoder.h @@ -32,10 +32,10 @@ namespace o2 namespace hmpid { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::HMP) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::HMP, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawFile.h b/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawFile.h deleted file mode 100644 index e92e8375ad0d0..0000000000000 --- a/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawFile.h +++ /dev/null @@ -1,63 +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 HmpidDecodeRawFile.h -/// \author Antonio Franco - INFN Bari -/// \brief Derived Class for decoding Raw Data File stream -/// \version 1.0 -/// \date 24 set 2020 - -#ifndef COMMON_HMPIDDECODERAWFILE_H_ -#define COMMON_HMPIDDECODERAWFILE_H_ - -#include -#include -#include -#include -#include -#include - -#include "HMPIDReconstruction/HmpidDecoder.h" - -#define MAXFILENAMEBUFFER 512 -#define MAXRAWFILEBUFFER RAWBLOCKDIMENSION_W * 4 + 8 - -namespace o2 -{ -namespace hmpid -{ - -class HmpidDecodeRawFile : public HmpidDecoder -{ - public: - HmpidDecodeRawFile(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments); - HmpidDecodeRawFile(int numOfEquipments); - ~HmpidDecodeRawFile(); - - bool setUpStream(void* InpuFileName, long Size); - - private: - bool getBlockFromStream(uint32_t** streamPtr, uint32_t Size); - bool getHeaderFromStream(uint32_t** streamPtr); - bool getWordFromStream(uint32_t* word); - int fileExists(char* filewithpath); - void setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge); - - private: - FILE* fh; - char mInputFile[MAXFILENAMEBUFFER]; - uint32_t mFileBuffer[MAXRAWFILEBUFFER]; -}; - -} // namespace hmpid -} // namespace o2 -#endif /* COMMON_HMPIDDECODERAWFILE_H_ */ diff --git a/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawMem.h b/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawMem.h deleted file mode 100644 index d5d82d0f238e9..0000000000000 --- a/Detectors/HMPID/reconstruction/include/HMPIDReconstruction/HmpidDecodeRawMem.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 HmpidDecodeRawMem.h -/// \author Antonio Franco - INFN Bari -/// \brief Derived Class for decoding Raw Data Memory stream -/// \version 1.0 -/// \date 24 set 2020 - -#ifndef COMMON_HMPIDDECODERAWMEM_H_ -#define COMMON_HMPIDDECODERAWMEM_H_ - -#include -#include -#include -#include -#include -#include - -#include "DataFormatsHMP/Digit.h" -#include "HMPIDBase/Geo.h" -#include "HMPIDReconstruction/HmpidDecoder.h" - -using namespace o2; - -namespace o2 -{ -namespace hmpid -{ - -class HmpidDecodeRawMem : public HmpidDecoder -{ - public: - HmpidDecodeRawMem(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments); - HmpidDecodeRawMem(int numOfEquipments); - ~HmpidDecodeRawMem(); - - bool setUpStream(void* Buffer, long BufferLen) override; - - private: - bool getBlockFromStream(uint32_t** streamPtr, uint32_t Size) override; - bool getHeaderFromStream(uint32_t** streamPtr) override; - bool getWordFromStream(uint32_t* word) override; - void setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge) override; - - private: -}; - -class HmpidDecodeRawDigit : public HmpidDecodeRawMem -{ - public: - HmpidDecodeRawDigit(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments); - HmpidDecodeRawDigit(int numOfEquipments); - ~HmpidDecodeRawDigit(); - - std::vector mDigits; - - private: - void setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge) override; -}; - -} // namespace hmpid -} // namespace o2 -#endif /* COMMON_HMPIDDECODERAWFILE_H_ */ diff --git a/Detectors/HMPID/reconstruction/src/HmpidDecodeRawFile.cxx b/Detectors/HMPID/reconstruction/src/HmpidDecodeRawFile.cxx deleted file mode 100644 index df97a4d2101e0..0000000000000 --- a/Detectors/HMPID/reconstruction/src/HmpidDecodeRawFile.cxx +++ /dev/null @@ -1,158 +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 HmpidDecodeRawFile.cxx -/// \author Antonio Franco - INFN Bari -/// \brief Derived Class for decoding Raw Data File stream -/// \version 1.0 -/// \date 24 set 2020 - -/* ------ HISTORY --------- -*/ -#include // for LOG -#include "Framework/Logger.h" - -#include "HMPIDReconstruction/HmpidDecodeRawFile.h" - -using namespace o2::hmpid; - -/// Constructor with the default HMPID equipments map at P2 -/// @param[in] numOfEquipments : number of defined equipments [0..13] -HmpidDecodeRawFile::HmpidDecodeRawFile(int numOfEquipments) - : HmpidDecoder(numOfEquipments) -{ - fh = 0; -} - -/// Constructor with the HMPID address map -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -/// @param[in] *EqIds : the pointer to the Equipments ID array -/// @param[in] *CruIds : the pointer to the CRU ID array -/// @param[in] *LinkIds : the pointer to the Link ID array -HmpidDecodeRawFile::HmpidDecodeRawFile(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments) - : HmpidDecoder(EqIds, CruIds, LinkIds, numOfEquipments) -{ - fh = 0; -} - -/// Destructor -HmpidDecodeRawFile::~HmpidDecodeRawFile() -{ -} - -/// Setup the Input Stream with a File Handle -/// verify the existence and try to open it -/// @param[in] *FileName : the string that contains the File Name -/// @param[in] Size : not used -/// @returns True if the file is opened -/// @throws TH_FILENOTEXISTS Thrown if the file doesn't exists -/// @throws TH_OPENFILE Thrown if Fails to open the file -bool HmpidDecodeRawFile::setUpStream(void* FileName, long Size) -{ - strcpy(mInputFile, (const char*)FileName); - // files section ---- - if (!fileExists(mInputFile)) { - LOG(error) << "The input file " << mInputFile << " does not exist at this time."; - throw TH_FILENOTEXISTS; - } - // open the file - fh = fopen(mInputFile, "rb"); - if (fh == 0) { - LOG(error) << "ERROR to open Input file ! [" << mInputFile << "]"; - throw TH_OPENFILE; - } - - mActualStreamPtr = 0; // sets the pointer to the Buffer - mEndStreamPtr = 0; //sets the End of buffer - mStartStreamPtr = 0; - - return (true); -} - -/// Gets a sized chunk from the stream. Read from the file and update the pointers -/// ATTENTION : in order to optimize the disk accesses the block read pre-load a -/// complete Header+Payload block, the Size parameter is recalculated with the -/// dimension of the pack extract from the header field 'Offeset' -/// -/// verify the existence and try to open it -/// @param[in] **streamPtr : the pointer to the memory buffer -/// @param[in] Size : not used -/// @returns True if the file is opened -/// @throws TH_WRONGFILELEN Thrown if the file doesn't contains enough words -bool HmpidDecodeRawFile::getBlockFromStream(uint32_t** streamPtr, uint32_t Size) -{ - if (Size > MAXRAWFILEBUFFER) - return (false); - int nr = fread(mFileBuffer, sizeof(int32_t), HEADERDIMENSION_W, fh); - if (nr != HEADERDIMENSION_W) { - throw TH_WRONGFILELEN; - } - Size = ((mFileBuffer[2] & 0x0000FFFF) / sizeof(int32_t)) - HEADERDIMENSION_W; - nr = fread(mFileBuffer + HEADERDIMENSION_W, sizeof(int32_t), Size, fh); - LOG(debug) << " getBlockFromStream read " << nr << " of " << Size + HEADERDIMENSION_W << " words !"; - if (nr != Size) { - throw TH_WRONGFILELEN; - } - *streamPtr = mFileBuffer; - mStartStreamPtr = mFileBuffer; - mActualStreamPtr = mFileBuffer; - mEndStreamPtr = mFileBuffer + Size; - return (true); -} - -/// Reads the Header from the file -/// @param[in] **streamPtr : the pointer to the memory buffer -/// @returns True if the header is read -bool HmpidDecodeRawFile::getHeaderFromStream(uint32_t** streamPtr) -{ - bool flag = getBlockFromStream(streamPtr, RAWBLOCKDIMENSION_W); // reads the 8k block - mActualStreamPtr += HEADERDIMENSION_W; // Move forward for the first word - return (flag); -} - -/// Read one word from the pre-load buffer -/// @param[in] *word : the buffer for the read word -/// @returns True every time -bool HmpidDecodeRawFile::getWordFromStream(uint32_t* word) -{ - *word = *mActualStreamPtr; - mActualStreamPtr++; - return (true); -} - -/// ----- Sets the Pad ! ------ -/// this is an overloaded method. In this version the value of the charge -/// is used to update the statistical matrix of the base class -/// -/// @param[in] *eq : the pointer to the Equipment object -/// @param[in] col : the column [0..23] -/// @param[in] dil : the dilogic [0..9] -/// @param[in] ch : the channel [0..47] -/// @param[in] charge : the value of the charge -void HmpidDecodeRawFile::setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge) -{ - eq->setPad(col, dil, ch, charge); - return; -} - -/// Checks if the file exists ! -/// @param[in] *filewithpath : the File Name to check -/// @returns True if the file exists -int HmpidDecodeRawFile::fileExists(char* filewithpath) -{ - if (access(filewithpath, F_OK) != -1) { - return (true); - } else { - return (false); - } -} -o2::hmpid::Digit diff --git a/Detectors/HMPID/reconstruction/src/HmpidDecodeRawMem.cxx b/Detectors/HMPID/reconstruction/src/HmpidDecodeRawMem.cxx deleted file mode 100644 index 5a4f2acbfd97b..0000000000000 --- a/Detectors/HMPID/reconstruction/src/HmpidDecodeRawMem.cxx +++ /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. - -/// -/// \file HmpidDecodeRawMem.cxx -/// \author Antonio Franco - INFN Bari -/// \brief Derived Class for decoding Raw Data Memory stream -/// \version 1.0 -/// \date 24 set 2020 - -/* ------ HISTORY --------- -*/ -#include // for LOG -#include "Framework/Logger.h" - -#include "DataFormatsHMP/Digit.h" -#include "HMPIDBase/Geo.h" -#include "HMPIDReconstruction/HmpidDecodeRawMem.h" - -using namespace o2::hmpid; - -/// Constructor : accepts the number of equipments to define -/// The mapping is the default at P2 -/// Allocates instances for all defined equipments -/// normally it is equal to 14 -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -HmpidDecodeRawMem::HmpidDecodeRawMem(int numOfEquipments) - : HmpidDecoder(numOfEquipments) -{ -} - -/// Constructor : accepts the number of equipments to define -/// and their complete address map -/// Allocates instances for all defined equipments -/// -/// The Address map is build from three array -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -/// @param[in] *EqIds : the pointer to the Equipments ID array -/// @param[in] *CruIds : the pointer to the CRU ID array -/// @param[in] *LinkIds : the pointer to the Link ID array -HmpidDecodeRawMem::HmpidDecodeRawMem(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments) - : HmpidDecoder(EqIds, CruIds, LinkIds, numOfEquipments) -{ -} - -/// Destructor -HmpidDecodeRawMem::~HmpidDecodeRawMem() = default; - -/// Setup the Input Stream with a Memory Pointer -/// the buffer length is in byte, some controls are done -/// -/// @param[in] *Buffer : the pointer to Memory buffer -/// @param[in] BufferLen : the length of the buffer (bytes) -/// @returns True if the stream is set -/// @throws TH_NULLBUFFERPOINTER Thrown if the pointer to the buffer is NULL -/// @throws TH_BUFFEREMPTY Thrown if the buffer is empty -/// @throws TH_WRONGBUFFERDIM Thrown if the buffer len is less then one header -bool HmpidDecodeRawMem::setUpStream(void* Buffer, long BufferLen) -{ - long wordsBufferLen = BufferLen / (sizeof(int32_t) / sizeof(char)); // Converts the len in words - if (Buffer == nullptr) { - LOG(error) << "Raw data buffer null Pointer ! "; - throw TH_NULLBUFFERPOINTER; - } - if (wordsBufferLen == 0) { - LOG(error) << "Raw data buffer Empty ! "; - throw TH_BUFFEREMPTY; - } - if (wordsBufferLen < 16) { - LOG(error) << "Raw data buffer less then the Header Dimension = " << wordsBufferLen; - throw TH_WRONGBUFFERDIM; - } - - mActualStreamPtr = (uint32_t*)Buffer; // sets the pointer to the Buffer - mEndStreamPtr = ((uint32_t*)Buffer) + wordsBufferLen; //sets the End of buffer - mStartStreamPtr = ((uint32_t*)Buffer); - // std::cout << " setUpStrem : StPtr=" << mStartStreamPtr << " EndPtr=" << mEndStreamPtr << " Len=" << wordsBufferLen << std::endl; - return (true); -} - -/// Gets a sized chunk from the stream. The stream pointers members are updated -/// @param[in] **streamPtr : the pointer to the memory buffer -/// @param[in] Size : the dimension of the chunk (words) -/// @returns True every time -/// @throw TH_WRONGBUFFERDIM Buffer length shorter then the requested -bool HmpidDecodeRawMem::getBlockFromStream(uint32_t** streamPtr, uint32_t Size) -{ - *streamPtr = mActualStreamPtr; - mActualStreamPtr += Size; - if (mActualStreamPtr > mEndStreamPtr) { - // std::cout << " getBlockFromStream : StPtr=" << mActualStreamPtr << " EndPtr=" << mEndStreamPtr << " Len=" << Size << std::endl; - // std::cout << "Beccato " << std::endl; - // throw TH_WRONGBUFFERDIM; - return (false); - } - return (true); -} - -/// Gets the Header Block from the stream. -/// @param[in] **streamPtr : the pointer to the memory buffer -/// @returns True if the header is read -bool HmpidDecodeRawMem::getHeaderFromStream(uint32_t** streamPtr) -{ - return (getBlockFromStream(streamPtr, mRDHSize)); -} - -/// Gets a Word from the stream. -/// @param[in] *word : the buffer for the read word -/// @returns True if the operation end well -bool HmpidDecodeRawMem::getWordFromStream(uint32_t* word) -{ - uint32_t* appo; - *word = *mActualStreamPtr; - return (getBlockFromStream(&appo, 1)); -} - -/// ----- Sets the Pad ! ------ -/// this is an overloaded method. In this version the value of the charge -/// is used to update the statistical matrix of the base class -/// -/// @param[in] *eq : the pointer to the Equipment object -/// @param[in] col : the column [0..23] -/// @param[in] dil : the dilogic [0..9] -/// @param[in] ch : the channel [0..47] -/// @param[in] charge : the value of the charge -void HmpidDecodeRawMem::setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge) -{ - eq->setPad(col, dil, ch, charge); - return; -} - -// ======================================================================================== - -/// Constructor : accepts the number of equipments to define -/// The mapping is the default at P2 -/// Allocates instances for all defined equipments -/// normally it is equal to 14 -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -HmpidDecodeRawDigit::HmpidDecodeRawDigit(int numOfEquipments) - : HmpidDecodeRawMem(numOfEquipments) -{ -} - -/// Constructor : accepts the number of equipments to define -/// and their complete address map -/// Allocates instances for all defined equipments -/// -/// The Address map is build from three array -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -/// @param[in] *EqIds : the pointer to the Equipments ID array -/// @param[in] *CruIds : the pointer to the CRU ID array -/// @param[in] *LinkIds : the pointer to the Link ID array -HmpidDecodeRawDigit::HmpidDecodeRawDigit(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments) - : HmpidDecodeRawMem(EqIds, CruIds, LinkIds, numOfEquipments) -{ -} - -/// Destructor -HmpidDecodeRawDigit::~HmpidDecodeRawDigit() = default; - -/// ----- Sets the Pad ! ------ -/// this is an overloaded method. In this version the value of the charge -/// is used to update the statistical matrix of the base class -/// -/// @param[in] *eq : the pointer to the Equipment object -/// @param[in] col : the column [0..23] -/// @param[in] dil : the dilogic [0..9] -/// @param[in] ch : the channel [0..47] -/// @param[in] charge : the value of the charge -void HmpidDecodeRawDigit::setPad(HmpidEquipment* eq, int col, int dil, int ch, uint16_t charge) -{ - eq->setPad(col, dil, ch, charge); - mDigits.push_back(o2::hmpid::Digit(charge, eq->getEquipmentId(), col, dil, ch)); - //std::cout << "DI " << mDigits.back() << " "< // for LOG -#include "Framework/Logger.h" -#include "Headers/RAWDataHeader.h" -#include "HMPIDReconstruction/HmpidDecoder.h" -#include "DataFormatsHMP/Digit.h" - -using namespace o2::hmpid; - -// ============= HmpidDecoder Class implementation ======= - -/// Decoding Error Messages Definitions -char HmpidDecoder::sErrorDescription[MAXERRORS][MAXDESCRIPTIONLENGHT] = {"Word that I don't known !", - "Row Marker Word with 0 words", "Duplicated Pad Word !", "Row Marker Wrong/Lost -> to EoE", - "Row Marker Wrong/Lost -> to EoE", "Row Marker reports an ERROR !", "Lost EoE Marker !", "Double EoE marker", - "Wrong size definition in EoE Marker", "Double Mark Word", "Wrong Size in Segment Marker", "Lost EoS Marker !", - "HMPID Header Errors"}; - -/// HMPID Firmware Error Messages Definitions -char HmpidDecoder::sHmpidErrorDescription[MAXHMPIDERRORS][MAXDESCRIPTIONLENGHT] = { - "L0 Missing," - "L1 is received without L0", - "L1A signal arrived before the L1 Latency", "L1A signal arrived after the L1 Latency", - "L1A is missing or L1 timeout", "L1A Message is missing or L1 Message"}; - -/// Constructor : accepts the number of equipments to define -/// The mapping is the default at P2 -/// Allocates instances for all defined equipments -/// normally it is equal to 14 -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -HmpidDecoder::HmpidDecoder(int numOfEquipments) -{ - // The standard definition of HMPID equipments at P2 - int EqIds[] = {0, 1, 2, 3, 4, 5, 8, 9, 6, 7, 10, 11, 12, 13}; - int CruIds[] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3}; - int LinkIds[] = {0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 0, 1, 2}; - - mNumberOfEquipments = numOfEquipments; - for (int i = 0; i < mNumberOfEquipments; i++) { - mTheEquipments[i] = new HmpidEquipment(EqIds[i], CruIds[i], LinkIds[i]); - } -} - -/// Constructor : accepts the number of equipments to define -/// and their complete address map -/// Allocates instances for all defined equipments -/// -/// The Address map is build from three array -/// @param[in] numOfEquipments : the number of equipments to define [1..14] -/// @param[in] *EqIds : the pointer to the Equipments ID array -/// @param[in] *CruIds : the pointer to the CRU ID array -/// @param[in] *LinkIds : the pointer to the Link ID array -HmpidDecoder::HmpidDecoder(int* EqIds, int* CruIds, int* LinkIds, int numOfEquipments) -{ - mNumberOfEquipments = numOfEquipments; - for (int i = 0; i < mNumberOfEquipments; i++) { - mTheEquipments[i] = new HmpidEquipment(EqIds[i], CruIds[i], LinkIds[i]); - } -} - -/// Destructor : remove the Equipments instances -HmpidDecoder::~HmpidDecoder() -{ - for (int i = 0; i < mNumberOfEquipments; i++) { - delete mTheEquipments[i]; - } -} - -/// Init all the members variables. -void HmpidDecoder::init() -{ - mRDHSize = sizeof(o2::header::RAWDataHeader) / sizeof(uint32_t); - - mVerbose = 0; - mHeEvent = 0; - mHeBusy = 0; - mNumberWordToRead = 0; - mPayloadTail = 0; - - mHeFEEID = 0; - mHeSize = 0; - mHeVer = 0; - mHePrior = 0; - mHeStop = 0; - mHePages = 0; - mEquipment = 0; - - mHeOffsetNewPack = 0; - mHeMemorySize = 0; - - mHeDetectorID = 0; - mHeDW = 0; - mHeCruID = 0; - mHePackNum = 0; - mHePAR = 0; - mHePageNum = 0; - mHeLinkNum = 0; - mHeFirmwareVersion = 0; - mHeHmpidError = 0; - mHeBCDI = 0; - mHeORBIT = 0; - mHeTType = 0; - - mActualStreamPtr = nullptr; - mEndStreamPtr = nullptr; - mStartStreamPtr = nullptr; - - for (int i = 0; i < mNumberOfEquipments; i++) { - mTheEquipments[i]->init(); - } -} - -/// Returns the Equipment Index (Pointer of the array) converting -/// the FLP hardware coords (CRU_Id and Link_Id) -/// @param[in] CruId : the CRU ID [0..3] -> FLP 160 = [0,1] FLP 161 = [2,3] -/// @param[in] LinkId : the Link ID [0..3] -/// @returns EquipmentIndex : the index in the Equipment array [0..13] (-1 := error) -int HmpidDecoder::getEquipmentIndex(int CruId, int LinkId) -{ - for (int i = 0; i < mNumberOfEquipments; i++) { - if (mTheEquipments[i]->getEquipmentId(CruId, LinkId) != -1) { - return (i); - } - } - return (-1); -} - -/// Returns the Equipment Index (Pointer of the array) converting -/// the Equipment_ID (Firmaware defined Id AKA FFEID) -/// @param[in] EquipmentId : the Equipment ID [0..13] -/// @returns EquipmentIndex : the index in the Equipment array [0..13] (-1 := error) -int HmpidDecoder::getEquipmentIndex(int EquipmentId) -{ - for (int i = 0; i < mNumberOfEquipments; i++) { - if (mTheEquipments[i]->getEquipmentId() == EquipmentId) { - return (i); - } - } - return (-1); -} - -/// Returns the Equipment_ID converting the FLP hardware coords -/// @param[in] CruId : the CRU ID [0..3] -> FLP 160 = [0,1] FLP 161 = [2,3] -/// @param[in] LinkId : the Link ID [0..3] -/// @returns EquipmentID : the ID of the Equipment [0..13] (-1 := error) -int HmpidDecoder::getEquipmentID(int CruId, int LinkId) -{ - for (int i = 0; i < mNumberOfEquipments; i++) { - if (mTheEquipments[i]->getEquipmentId(CruId, LinkId) != -1) { - return (mTheEquipments[i]->getEquipmentId()); - } - } - return (-1); -} - -/// Scans the BitMap of Raw Data File word and detect the type -/// and the parameters -/// @param[in] wp : the word to analyze -/// @param[out] *p1 : first parameter extract (if it exists) -/// @param[out] *p2 : second parameter extract (if it exists) -/// @param[out] *p3 : third parameter extract (if it exists) -/// @param[out] *p4 : fourth parameter extract (if it exists) -/// @returns Type of Word : the type of word [0..4] (0 := undetect) -int HmpidDecoder::checkType(uint32_t wp, int* p1, int* p2, int* p3, int* p4) -{ - if ((wp & 0x0000ffff) == 0x000036A8 || (wp & 0x0000ffff) == 0x000032A8 || (wp & 0x0000ffff) == 0x000030A0 || (wp & 0x0800ffff) == 0x080010A0) { - *p2 = (wp & 0x03ff0000) >> 16; // Number of words of row - *p1 = wp & 0x0000ffff; - return (WTYPE_ROW); - } - if ((wp & 0xfff00000) >> 20 == 0xAB0) { - *p2 = (wp & 0x000fff00) >> 8; // Number of words of Segment - *p1 = (wp & 0xfff00000) >> 20; - *p3 = wp & 0x0000000F; - if (*p3 < 4 && *p3 > 0) { - return (WTYPE_EOS); - } - } - // #EX MASK Raul 0x3803FF80 # ex mask 0xF803FF80 - this is EoE marker 0586800B0 - if ((wp & 0x0803FF80) == 0x08000080) { - *p1 = (wp & 0x07c00000) >> 22; - *p2 = (wp & 0x003C0000) >> 18; - *p3 = (wp & 0x0000007F); - if (*p1 < 25 && *p2 < 11) { - return (WTYPE_EOE); - } - } - if ((wp & 0x08000000) == 0) { // # this is a pad - // PAD:0000.0ccc.ccdd.ddnn.nnnn.vvvv.vvvv.vvvv :: c=col,d=dilo,n=chan,v=value - *p1 = (wp & 0x07c00000) >> 22; - *p2 = (wp & 0x003C0000) >> 18; - *p3 = (wp & 0x0003F000) >> 12; - *p4 = (wp & 0x00000FFF); - if (*p1 > 0 && *p1 < 25 && *p2 > 0 && *p2 < 11 && *p3 < 48) { - return (WTYPE_PAD); - } - } else { - return (WTYPE_NONE); - } - return (WTYPE_NONE); -} - -/// Checks if is a Raw Marker and extract the Row Size -/// @param[in] wp : the word to check -/// @param[out] *Err : true if an error is detected -/// @param[out] *rowSize : the number of words of the row -/// @param[out] *mark : the row marker -/// @returns True if Row Marker is detected -bool HmpidDecoder::isRowMarker(uint32_t wp, int* Err, int* rowSize, int* mark) -{ - if ((wp & 0x0000ffff) == 0x36A8 || (wp & 0x0000ffff) == 0x32A8 || (wp & 0x0000ffff) == 0x30A0 || (wp & 0x0800ffff) == 0x080010A0) { - *rowSize = (wp & 0x03ff0000) >> 16; // # Number of words of row - *mark = wp & 0x0000ffff; - *Err = false; - return (true); - } else { - *Err = true; - return (false); - } -} - -/// Checks if is a Segment Marker and extracts the Segment number and the size -/// @param[in] wp : the word to check -/// @param[out] *Err : true if an error is detected -/// @param[out] *segSize : the number of words of the segment -/// @param[out] *Seg : the Segment number [1..3] -/// @param[out] *mark : the Segment Marker -/// @returns True if Segment Marker is detected -bool HmpidDecoder::isSegmentMarker(uint32_t wp, int* Err, int* segSize, int* Seg, int* mark) -{ - *Err = false; - if ((wp & 0xfff00000) >> 20 == 0xAB0) { - *segSize = (wp & 0x000fff00) >> 8; // # Number of words of Segment - *mark = (wp & 0xfff00000) >> 20; - *Seg = wp & 0x0000000F; - if (*Seg > 3 || *Seg < 1) { - LOG(info) << " Wrong segment Marker Word, bad Number of segment" << *Seg << "!"; - *Err = true; - } - return (true); - } else { - return (false); - } -} - -/// Checks if is a PAD Word and extracts all the parameters -/// PAD map : 0000.0ccc.ccdd.ddnn.nnnn.vvvv.vvvv.vvvv :: c=col,d=dilo,n=chan,v=value -/// @param[in] wp : the word to check -/// @param[out] *Err : true if an error is detected -/// @param[out] *Col : the column number [1..24] -/// @param[out] *Dilogic : the dilogic number [1..10] -/// @param[out] *Channel : the channel number [0..47] -/// @param[out] *Charge : the value of Charge [0..4095] -/// @returns True if PAD Word is detected -bool HmpidDecoder::isPadWord(uint32_t wp, int* Err, int* Col, int* Dilogic, int* Channel, int* Charge) -{ - *Err = false; - // if ((wp & 0x08000000) != 0) { - if ((wp & 0x08000000) != 0) { - return (false); - } - *Col = (wp & 0x07c00000) >> 22; - *Dilogic = (wp & 0x003C0000) >> 18; - *Channel = (wp & 0x0003F000) >> 12; - *Charge = (wp & 0x00000FFF); - - if ((wp & 0x0ffff) == 0x036A8 || (wp & 0x0ffff) == 0x032A8 || (wp & 0x0ffff) == 0x030A0 || (wp & 0x0ffff) == 0x010A0) { // # ! this is a pad - if (*Dilogic > 10 || *Channel > 47 || *Dilogic < 1 || *Col > 24 || *Col < 1) { - return (false); - } - } else { - if (*Dilogic > 10 || *Channel > 47 || *Dilogic < 1 || *Col > 24 || *Col < 1) { - // LOG(warning) << " Wrong Pad values Col=" << *Col << " Dilogic=" << *Dilogic << " Channel=" << *Channel << " Charge=" << *Charge << " wp:0x" << std::hex << wp << std::dec; - *Err = true; - return (false); - } - } - return (true); -} - -/// Checks if is a EoE Marker and extracts the Column, Dilogic and the size -/// @param[in] wp : the word to check -/// @param[out] *Err : true if an error is detected -/// @param[out] *Col : the column number [1..24] -/// @param[out] *Dilogic : the dilogic number [1..10] -/// @param[out] *Eoesize : the number of words for dilogic -/// @returns True if EoE marker is detected -bool HmpidDecoder::isEoEmarker(uint32_t wp, int* Err, int* Col, int* Dilogic, int* Eoesize) -{ - *Err = false; - // #EX MASK Raul 0x3803FF80 # ex mask 0xF803FF80 - this is EoE marker 0586800B0 - if ((wp & 0x0803FF80) == 0x08000080) { - *Col = (wp & 0x07c00000) >> 22; - *Dilogic = (wp & 0x003C0000) >> 18; - *Eoesize = (wp & 0x0000007F); - if (*Col > 24 || *Dilogic > 10) { - LOG(info) << " EoE size wrong definition. Col=" << *Col << " Dilogic=" << *Dilogic; - *Err = true; - } - return (true); - } else { - return (false); - } -} - -/// Decode the HMPID error BitMap field (5 bits) and returns true if there are -/// errors and in addition the concat string that contains the error messages -/// ATTENTION : the char * outbuf MUST point to a 250 bytes buffer -/// @param[in] ErrorField : the HMPID Error field -/// @param[out] *outbuf : the output buffer that contains the error description -/// @returns True if EoE marker is detected -bool HmpidDecoder::decodeHmpidError(int ErrorField, char* outbuf) -{ - int res = false; - outbuf[0] = '\0'; - for (int i = 0; i < MAXHMPIDERRORS; i++) { - if ((ErrorField & (0x01 << i)) != 0) { - res = true; - strcat(outbuf, sHmpidErrorDescription[i]); - } - } - return (res); -} - -/// This Decode the Raw Data Header, returns the EquipmentIndex -/// that is obtained with the FLP hardware coords -/// -/// ATTENTION : the 'EquipIndex' parameter and the mEquipment member -/// are different data: the first is the pointer in the Equipments instances -/// array, the second is the FEE_ID number -/// -/// The EVENT_NUMBER : actually is calculated from the ORBIT number -/// -/// @param[in] *streamPtrAdr : the pointer to the Header buffer -/// @param[out] *EquipIndex : the Index to the Equipment Object Array [0..13] -/// @returns True every time -/// @throws TH_WRONGEQUIPINDEX Thrown if the Equipment Index is out of boundary (Equipment not recognized) -int HmpidDecoder::decodeHeader(uint32_t* streamPtrAdr, int* EquipIndex) -{ - uint32_t* buffer = streamPtrAdr; // Sets the pointer to buffer - o2::header::RAWDataHeader* hpt = (o2::header::RAWDataHeader*)buffer; - - /* - mHeFEEID = (buffer[0] & 0x000f0000) >> 16; - mHeSize = (buffer[0] & 0x0000ff00) >> 8; - mHeVer = (buffer[0] & 0x000000ff); - mHePrior = (buffer[1] & 0x000000FF); - mHeDetectorID = (buffer[1] & 0x0000FF00) >> 8; - mHeOffsetNewPack = (buffer[2] & 0x0000FFFF); - mHeMemorySize = (buffer[2] & 0xffff0000) >> 16; - mHeDW = (buffer[3] & 0xF0000000) >> 24; - mHeCruID = (buffer[3] & 0x0FF0000) >> 16; - mHePackNum = (buffer[3] & 0x0000FF00) >> 8; - mHeLinkNum = (buffer[3] & 0x000000FF); - mHeBCDI = (buffer[4] & 0x00000FFF); - mHeORBIT = buffer[5]; - mHeTType = buffer[8]; - mHePageNum = (buffer[9] & 0x0000FFFF); - mHeStop = (buffer[9] & 0x00ff0000) >> 16; - mHeBusy = (buffer[12] & 0xfffffe00) >> 9; - mHeFirmwareVersion = buffer[12] & 0x0000000f; - mHeHmpidError = (buffer[12] & 0x000001F0) >> 4; - mHePAR = buffer[13] & 0x0000FFFF; - */ - mHeFEEID = hpt->feeId; - mHeSize = hpt->headerSize; - mHeVer = hpt->version; - mHePrior = hpt->priority; - mHeDetectorID = hpt->sourceID; - mHeOffsetNewPack = hpt->offsetToNext; - mHeMemorySize = hpt->memorySize; - mHeDW = hpt->endPointID; - mHeCruID = hpt->cruID; - mHePackNum = hpt->packetCounter; - mHeLinkNum = hpt->linkID; - mHeBCDI = hpt->bunchCrossing; - mHeORBIT = hpt->orbit; - mHeTType = hpt->triggerType; - mHePageNum = hpt->pageCnt; - mHeStop = hpt->stop; - mHeBusy = (hpt->detectorField & 0xfffffe00) >> 9; - mHeFirmwareVersion = hpt->detectorField & 0x0000000f; - mHeHmpidError = (hpt->detectorField & 0x000001F0) >> 4; - mHePAR = hpt->detectorPAR; - - *EquipIndex = getEquipmentIndex(mHeCruID, mHeLinkNum); - // mEquipment = (*EquipIndex != -1) ? mTheEquipments[*EquipIndex]->getEquipmentId() : -1; - mEquipment = mHeFEEID & 0x000F; - mNumberWordToRead = ((mHeMemorySize - mHeSize) / sizeof(uint32_t)); - mPayloadTail = ((mHeOffsetNewPack - mHeMemorySize) / sizeof(uint32_t)); - - // ---- Event ID : Actualy based on ORBIT NUMBER and BC - mHeEvent = (mHeORBIT << 12) | mHeBCDI; - - LOG(debug) << "FEE-ID=" << mHeFEEID << " HeSize=" << mHeSize << " HePrior=" << mHePrior << " Det.Id=" << mHeDetectorID << " HeMemorySize=" << mHeMemorySize << " HeOffsetNewPack=" << mHeOffsetNewPack; - LOG(debug) << " Equipment=" << mEquipment << " PakCounter=" << mHePackNum << " Link=" << mHeLinkNum << " CruID=" << mHeCruID << " DW=" << mHeDW << " BC=" << mHeBCDI << " ORBIT=" << mHeORBIT; - LOG(debug) << " TType=" << mHeTType << " HeStop=" << mHeStop << " PagesCounter=" << mHePageNum << " FirmVersion=" << mHeFirmwareVersion << " BusyTime=" << mHeBusy << " Error=" << mHeHmpidError << " PAR=" << mHePAR; - LOG(debug) << " EquIdx = " << *EquipIndex << " Event = " << mHeEvent << " Payload : Words to read=" << mNumberWordToRead << " PailoadTail=" << mPayloadTail; - - if (*EquipIndex == -1) { - LOG(error) << "ERROR ! Bad equipment Number: " << mEquipment; - throw TH_WRONGEQUIPINDEX; - } - // std::cout << "HMPID ! Exit decode header" << std::endl; - return (true); -} - -/// Updates some information related to the Event -/// this function is called at the end of the event -/// @param[in] *eq : the pointer to the Equipment Object -void HmpidDecoder::updateStatistics(HmpidEquipment* eq) -{ - eq->mPadsPerEventAverage = ((eq->mPadsPerEventAverage * (eq->mNumberOfEvents - 1)) + eq->mSampleNumber) / (eq->mNumberOfEvents); - eq->mEventSizeAverage = ((eq->mEventSizeAverage * (eq->mNumberOfEvents - 1)) + eq->mEventSize) / (eq->mNumberOfEvents); - eq->mBusyTimeAverage = ((eq->mBusyTimeAverage * eq->mBusyTimeSamples) + eq->mBusyTimeValue) / (++(eq->mBusyTimeSamples)); - if (eq->mSampleNumber == 0) { - eq->mNumberOfEmptyEvents += 1; - } - if (eq->mErrorsCounter > 0) { - eq->mNumberOfWrongEvents += 1; - } - eq->mTotalPads += eq->mSampleNumber; - eq->mTotalErrors += eq->mErrorsCounter; - - //std::cout << ">>>updateStatistics() >>> "<< eq->getEquipmentId() << "="<< eq->mNumberOfEvents<<" :" << eq->mEventSize <<","<< eq->mTotalPads << ", " << eq->mSampleNumber << std::endl; - - return; -} - -/// Evaluates the content of the header and detect the change of the event -/// with the relevant updates... -/// @param[in] EquipmentIndex : the pointer to the Array of Equipments Array -/// @returns the Pointer to the modified Equipment object -HmpidEquipment* HmpidDecoder::evaluateHeaderContents(int EquipmentIndex) -{ - //std::cout << "Enter evaluateHeaderContents.."; - HmpidEquipment* eq = mTheEquipments[EquipmentIndex]; - if (mHeEvent != eq->mEventNumber) { // Is a new event - if (eq->mEventNumber != OUTRANGEEVENTNUMBER) { // skip the first - updateStatistics(eq); // update previous statistics - } - eq->mNumberOfEvents++; - eq->mEventNumber = mHeEvent; - eq->mBusyTimeValue = mHeBusy * 0.00000005; - eq->mEventSize = 0; // reset the event - eq->mSampleNumber = 0; - eq->mErrorsCounter = 0; - mIntReco = {(uint16_t)mHeBCDI, (uint32_t)mHeORBIT}; - } - eq->mEventSize += mNumberWordToRead * sizeof(uint32_t); // Calculate the size in bytes - if (mHeHmpidError != 0) { - LOG(info) << "HMPID Header reports an error : " << mHeHmpidError; - dumpHmpidError(mHeHmpidError); - eq->setError(ERR_HMPID); - } - // std::cout << ".. end evaluateHeaderContents = " << eq->mEventNumber << std::endl; - return (eq); -} - -/// --------------- Decode One Page from Data Buffer --------------- -/// Read the stream, decode the contents and store resuls. -/// ATTENTION : Assumes that the input stream was set -/// @throws TH_WRONGHEADER Thrown if the Fails to decode the Header -/// @param[in] streamBuf : the pointer to the Pointer of the Stream Buffer -void HmpidDecoder::decodePage(uint32_t** streamBuf) -{ - int equipmentIndex; - try { - getHeaderFromStream(streamBuf); - } catch (int e) { - // The stream end ! - LOG(debug) << "End main decoding loop !"; - throw TH_BUFFEREMPTY; - } - try { - decodeHeader(*streamBuf, &equipmentIndex); - } catch (int e) { - LOG(error) << "Failed to decode the Header !"; - throw TH_WRONGHEADER; - } - - HmpidEquipment* eq = evaluateHeaderContents(equipmentIndex); - - uint32_t wpprev = 0; - uint32_t wp = 0; - int newOne = true; - int p1, p2, p3, p4; - int error; - int type; - bool isIt; - - int payIndex = 0; - while (payIndex < mNumberWordToRead) { //start the payload loop word by word - if (newOne == true) { - wpprev = wp; - if (!getWordFromStream(&wp)) { // end the stream - break; - } - type = checkType(wp, &p1, &p2, &p3, &p4); - if (type == WTYPE_NONE) { - if (eq->mWillBePad == true) { // try to recover the first pad ! - type = checkType((wp & 0xF7FFFFFF), &p1, &p2, &p3, &p4); - if (type == WTYPE_PAD && p3 == 0 && eq->mWordsPerDilogicCounter == 0) { - newOne = false; // # reprocess as pad - continue; - } - } - eq->setError(ERR_NOTKNOWN); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_NOTKNOWN] << " [" << wp << "]"; - eq->mWordsPerRowCounter++; - eq->mWordsPerSegCounter++; - payIndex++; - continue; - } - } - if (mEquipment == 8) { - LOG(info) << "Event" << eq->mEventNumber << " >" << std::hex << wp << std::dec << "<" << type; - } - if (eq->mWillBeRowMarker == true) { // #shoud be a Row Marker - if (type == WTYPE_ROW) { - eq->mColumnCounter++; - eq->mWordsPerSegCounter++; - eq->mRowSize = p2; - switch (p2) { - case 0: // Empty column - eq->setError(ERR_ROWMARKEMPTY); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_ROWMARKEMPTY] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = true; - break; - case 0x3FF: // Error in column - eq->setError(ERR_ROWMARKERROR); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_ROWMARKERROR] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = true; - break; - case 0x3FE: // Masked column - LOG(info) << "Equip=" << mEquipment << "The column=" << (eq->mSegment) * 8 + eq->mColumnCounter << " is Masked !"; - eq->mWillBeRowMarker = true; - break; - default: - eq->mWillBeRowMarker = false; - eq->mWillBePad = true; - break; - } - newOne = true; - } else { - if (wp == wpprev) { - eq->setError(ERR_DUPLICATEPAD); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_DUPLICATEPAD] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - newOne = true; - } else if (type == WTYPE_EOE) { // # Could be a EoE - eq->mColumnCounter++; - eq->setError(ERR_ROWMARKWRONG); - eq->mWillBeRowMarker = false; - eq->mWillBePad = true; - newOne = true; - } else if (type == WTYPE_PAD) { //# Could be a PAD - eq->mColumnCounter++; - eq->setError(ERR_ROWMARKLOST); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_ROWMARKLOST] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = false; - eq->mWillBePad = true; - newOne = true; - } else if (type == WTYPE_EOS) { // # Could be a EoS - eq->mWillBeRowMarker = false; - eq->mWillBeSegmentMarker = true; - newOne = false; - } else { - eq->mColumnCounter++; - eq->setError(ERR_ROWMARKLOST); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_ROWMARKLOST] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = false; - eq->mWillBePad = true; - newOne = true; - } - } - } else if (eq->mWillBePad == true) { // # We expect a pad - //# PAD:0000.0ccc.ccdd.ddnn.nnnn.vvvv.vvvv.vvvv :: c=col,d=dilo,n=chan,v=value - // c = 1..24 d = 1..10 n = 0..47 - if (type == WTYPE_PAD) { - newOne = true; - if (wp == wpprev) { - eq->setError(ERR_DUPLICATEPAD); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_DUPLICATEPAD] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - } else if (p1 != (eq->mSegment * 8 + eq->mColumnCounter)) { // # Manage - // We try to recover the RowMarker misunderstanding - isIt = isRowMarker(wp, &error, &p2, &p1); - if (isIt == true && error == false) { - type = WTYPE_ROW; - newOne = false; - eq->mWillBeEoE = true; - eq->mWillBePad = false; - } else { - LOG(debug) << "Equip=" << mEquipment << " Mismatch in column" - << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mColumnCounter = p1 % 8; - } - } else { - setPad(eq, p1 - 1, p2 - 1, p3, p4); - if (mEquipment == 8) { - LOG(info) << "Event" << eq->mEventNumber << " >" << p1 - 1 << "," << p2 - 1 << "," << p3 << "," << p4; - } - eq->mWordsPerDilogicCounter++; - eq->mSampleNumber++; - if (p3 == 47) { - eq->mWillBeEoE = true; - eq->mWillBePad = false; - } - } - eq->mWordsPerRowCounter++; - eq->mWordsPerSegCounter++; - } else if (type == WTYPE_EOE) { //# the pads are end ok - eq->mWillBeEoE = true; - eq->mWillBePad = false; - newOne = false; - } else if (type == WTYPE_ROW) { // # We Lost the EoE ! - // We try to recover the PAD misunderstanding - isIt = isPadWord(wp, &error, &p1, &p2, &p3, &p4); - if (isIt == true && error == false) { - type = WTYPE_PAD; - newOne = false; // # reprocess as pad - } else { - eq->setError(ERR_LOSTEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOEMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = true; - eq->mWillBePad = false; - newOne = false; - } - } else if (type == WTYPE_EOS) { // # We Lost the EoE ! - eq->setError(ERR_LOSTEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOEMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeSegmentMarker = true; - eq->mWillBePad = false; - newOne = false; - } - } else if (eq->mWillBeEoE == true) { // # We expect a EoE - if (type == WTYPE_EOE) { - eq->mWordsPerRowCounter++; - eq->mWordsPerSegCounter++; - if (wpprev == wp) { - eq->setError(ERR_DOUBLEEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_DOUBLEEOEMARK] << " col=" << p1; - } else if (p3 != eq->mWordsPerDilogicCounter) { - eq->setError(ERR_WRONGSIZEINEOE); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_WRONGSIZEINEOE] << " col=" << p1; - } - eq->mWordsPerDilogicCounter = 0; - if (p2 == 10) { - if (p1 % 8 != 0) { // # we expect the Row Marker - eq->mWillBeRowMarker = true; - } else { - eq->mWillBeSegmentMarker = true; - } - } else { - eq->mWillBePad = true; - } - eq->mWillBeEoE = false; - newOne = true; - } else if (type == WTYPE_EOS) { // We Lost the EoE ! - eq->setError(ERR_LOSTEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOEMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeSegmentMarker = true; - eq->mWillBeEoE = false; - newOne = false; - } else if (type == WTYPE_ROW) { //# We Lost the EoE ! - eq->setError(ERR_LOSTEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOEMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeRowMarker = true; - eq->mWillBeEoE = false; - newOne = false; - } else if (type == WTYPE_PAD) { // # We Lost the EoE ! - int typb, p1b, p2b, p3b, p4b; - typb = checkType((wp | 0x08000000), &p1b, &p2b, &p3b, &p4b); - if (typb == WTYPE_EOE && p3b == 48) { - type = typb; - p1 = p1b; - p2 = p2b; - p3 = p3b; - p4 = p4b; - newOne = false; // # reprocess as EoE - } else { - eq->setError(ERR_LOSTEOEMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOEMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBePad = true; - eq->mWillBeEoE = false; - newOne = false; - } - } - } else if (eq->mWillBeSegmentMarker == true) { // # We expect a EoSegment - if (wpprev == wp) { - eq->setError(ERR_DOUBLEMARKWORD); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_DOUBLEMARKWORD] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - newOne = true; - } else if (type == 2) { - if (abs(eq->mWordsPerSegCounter - p2) > 5) { - eq->setError(ERR_WRONGSIZESEGMENTMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_WRONGSIZESEGMENTMARK] << " Seg=" << p2; - } - eq->mWordsPerSegCounter = 0; - eq->mWordsPerRowCounter = 0; - eq->mColumnCounter = 0; - eq->mSegment = p3 % 3; - eq->mWillBeRowMarker = true; - eq->mWillBeSegmentMarker = false; - newOne = true; - } else { - eq->setError(ERR_LOSTEOSMARK); - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_LOSTEOSMARK] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << p1 << "]"; - eq->mWillBeSegmentMarker = false; - eq->mWillBeRowMarker = true; - newOne = false; - } - } - if (newOne) { - payIndex += 1; - } - } - for (int i = 0; i < mPayloadTail; i++) { // move the pointer to skip the Payload Tail - getWordFromStream(&wp); - } -} - -/// --------------- Read Raw Data Buffer --------------- -/// Read the stream, decode the contents and store resuls. -/// ATTENTION : Assumes that the input stream was set -/// @throws TH_WRONGHEADER Thrown if the Fails to decode the Header -bool HmpidDecoder::decodeBuffer() -{ - // ---------resets the PAdMap----------- - for (int i = 0; i < mNumberOfEquipments; i++) { - mTheEquipments[i]->init(); - mTheEquipments[i]->resetPadMap(); - mTheEquipments[i]->resetErrors(); - } - - int type; - int equipmentIndex = -1; - int isIt; - HmpidEquipment* eq; - uint32_t* streamBuf; - LOG(debug) << "Enter decoding !"; - - // Input Stream Main Loop - while (true) { - try { - decodePage(&streamBuf); - } catch (int e) { - LOG(debug) << "End main buffer decoding loop !"; - break; - } - } // this is the end of stream - - // cycle in order to update info for the last event - for (int i = 0; i < mNumberOfEquipments; i++) { - if (mTheEquipments[i]->mNumberOfEvents > 0) { - updateStatistics(mTheEquipments[i]); - } - } - return (true); -} - -/// --------- Decode One Page from Data Buffer with Fast Decoding -------- -/// Read the stream, decode the contents and store resuls. -/// ATTENTION : Assumes that the input stream was set -/// @throws TH_WRONGHEADER Thrown if the Fails to decode the Header -/// @param[in] streamBuf : the pointer to the Pointer of the Stream Buffer -void HmpidDecoder::decodePageFast(uint32_t** streamBuf) -{ - int equipmentIndex; - try { - getHeaderFromStream(streamBuf); - } catch (int e) { - // The stream end ! - LOG(info) << "End Fast Page decoding loop !"; - throw TH_BUFFEREMPTY; - } - try { - decodeHeader(*streamBuf, &equipmentIndex); - } catch (int e) { - LOG(info) << "Failed to decode the Header !"; - throw TH_WRONGHEADER; - } - HmpidEquipment* eq = evaluateHeaderContents(equipmentIndex); - uint32_t wpprev = 0; - uint32_t wp = 0; - int newOne = true; - int Column, Dilogic, Channel, Charge; - int pwer; - int payIndex = 0; - while (payIndex < mNumberWordToRead) { //start the payload loop word by word - wpprev = wp; - if (!getWordFromStream(&wp)) { // end the stream - break; - } - if (wp == wpprev) { - LOG(debug) << "Equip=" << mEquipment << sErrorDescription[ERR_DUPLICATEPAD] << " col=" << (eq->mSegment) * 8 + eq->mColumnCounter << "[" << Column << "]"; - } else { - if (isPadWord(wp, &pwer, &Column, &Dilogic, &Channel, &Charge) == true) { - if (pwer != true) { - setPad(eq, Column - 1, Dilogic - 1, Channel, Charge); - eq->mSampleNumber++; - } - } - } - payIndex += 1; - } - for (int i = 0; i < mPayloadTail; i++) { // move the pointer to skip the Payload Tail - getWordFromStream(&wp); - } - return; -} -/// ---------- Read Raw Data Buffer with Fast Decoding ---------- -/// Read the stream, decode the contents and store resuls. -/// Fast alghoritm : no parsing of control words ! -/// ATTENTION : Assumes that the input stream was set -/// @throws TH_WRONGHEADER Thrown if the Fails to decode the Header -bool HmpidDecoder::decodeBufferFast() -{ - // ---------resets the PAdMap----------- - for (int i = 0; i < mNumberOfEquipments; i++) { - mTheEquipments[i]->init(); - mTheEquipments[i]->resetPadMap(); - } - - uint32_t* streamBuf; - LOG(info) << "Enter FAST decoding !"; - - // Input Stream Main Loop - while (true) { - try { - decodePageFast(&streamBuf); - } catch (int e) { - LOG(info) << " End Buffer Fast Decoding !"; - break; - } - } // this is the end of stream - - // cycle in order to update info for the last event - for (int i = 0; i < mNumberOfEquipments; i++) { - if (mTheEquipments[i]->mNumberOfEvents > 0) { - updateStatistics(mTheEquipments[i]); - } - } - return (true); -} - -// ========================================================= - -/// Getter method to extract Statistic Data in Digit Coords -/// @param[in] Module : the HMPID Module number [0..6] -/// @param[in] Column : the HMPID Module Column number [0..143] -/// @param[in] Row : the HMPID Module Row number [0..159] -/// @returns The Number of entries for specified pad -uint16_t HmpidDecoder::getPadSamples(int Module, int Row, int Column) -{ - int e, c, d, h; - o2::hmpid::Digit::absolute2Equipment(Module, Row, Column, &e, &c, &d, &h); - int EqInd = getEquipmentIndex(e); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSamples[c][d][h]); -} - -/// Getter method to extract Statistic Data in Digit Coords -/// @param[in] Module : the HMPID Module number [0..6] -/// @param[in] Column : the HMPID Module Column number [0..143] -/// @param[in] Row : the HMPID Module Row number [0..159] -/// @returns The Sum of Charges for specified pad -double HmpidDecoder::getPadSum(int Module, int Row, int Column) -{ - int e, c, d, h; - o2::hmpid::Digit::absolute2Equipment(Module, Row, Column, &e, &c, &d, &h); - int EqInd = getEquipmentIndex(e); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSum[c][d][h]); -} - -/// Getter method to extract Statistic Data in Digit Coords -/// @param[in] Module : the HMPID Module number [0..6] -/// @param[in] Column : the HMPID Module Column number [0..143] -/// @param[in] Row : the HMPID Module Row number [0..159] -/// @returns The Sum of Square Charges for specified pad -double HmpidDecoder::getPadSquares(int Module, int Row, int Column) -{ - int e, c, d, h; - o2::hmpid::Digit::absolute2Equipment(Module, Row, Column, &e, &c, &d, &h); - int EqInd = getEquipmentIndex(e); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSquares[c][d][h]); -} - -/// Getter method to extract Statistic Data in Hardware Coords -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @param[in] Column : the HMPID Module Column number [0..23] -/// @param[in] Dilogic : the HMPID Module Row number [0..9] -/// @param[in] Channel : the HMPID Module Row number [0..47] -/// @returns The Number of Entries for specified pad -uint16_t HmpidDecoder::getChannelSamples(int EquipmId, int Column, int Dilogic, int Channel) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSamples[Column][Dilogic][Channel]); -} - -/// Getter method to extract Statistic Data in Hardware Coords -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @param[in] Column : the HMPID Module Column number [0..23] -/// @param[in] Dilogic : the HMPID Module Row number [0..9] -/// @param[in] Channel : the HMPID Module Row number [0..47] -/// @returns The Sum of Charges for specified pad -double HmpidDecoder::getChannelSum(int EquipmId, int Column, int Dilogic, int Channel) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSum[Column][Dilogic][Channel]); -} - -/// Getter method to extract Statistic Data in Hardware Coords -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @param[in] Column : the HMPID Module Column number [0..23] -/// @param[in] Dilogic : the HMPID Module Row number [0..9] -/// @param[in] Channel : the HMPID Module Row number [0..47] -/// @returns The Sum of Square Charges for specified pad -double HmpidDecoder::getChannelSquare(int EquipmId, int Column, int Dilogic, int Channel) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return (0); - } - return (mTheEquipments[EqInd]->mPadSquares[Column][Dilogic][Channel]); -} - -/// Gets the Average Event Size value -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @returns The Average Event Size value ( 0 for wrong Equipment Id) -float HmpidDecoder::getAverageEventSize(int EquipmId) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return (0.0); - } - return (mTheEquipments[EqInd]->mEventSizeAverage); -} - -/// Gets the Average Busy Time value -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @returns The Average Busy Time value ( 0 for wrong Equipment Id) -float HmpidDecoder::getAverageBusyTime(int EquipmId) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return (0.0); - } - return (mTheEquipments[EqInd]->mBusyTimeAverage); -} - -// =================================================== -// Methods to dump info - -/// Prints on the standard output the table of decoding -/// errors for one equipment -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -void HmpidDecoder::dumpErrors(int EquipmId) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return; - } - std::cout << "Dump Errors for the Equipment = " << EquipmId << std::endl; - for (int i = 0; i < MAXERRORS; i++) { - std::cout << sErrorDescription[i] << " = " << mTheEquipments[EqInd]->mErrors[i] << std::endl; - } - std::cout << " -------- " << std::endl; - return; -} - -/// Prints on the standard output a Table of statistical -/// decoding information for one equipment -/// @param[in] EquipmId : the HMPID EquipmentId [0..13] -/// @type[in] The type of info. 0 = Entries, 1 = Sum, 2 = Sum of squares -void HmpidDecoder::dumpPads(int EquipmId, int type) -{ - int EqInd = getEquipmentIndex(EquipmId); - if (EqInd < 0) { - return; - } - int Module = EquipmId / 2; - int StartRow = (EquipmId % 2 == 1) ? 80 : 0; - int EndRow = (EquipmId % 2 == 1) ? 160 : 80; - std::cout << "Dump Pads for the Equipment = " << EquipmId << std::endl; - for (int c = 0; c < 144; c++) { - for (int r = StartRow; r < EndRow; r++) { - switch (type) { - case 0: - std::cout << getPadSamples(Module, r, c) << ","; - break; - case 1: - std::cout << getPadSum(Module, r, c) << ","; - break; - case 2: - std::cout << getPadSquares(Module, r, c) << ","; - break; - } - } - std::cout << std::endl; - } - std::cout << " -------- " << std::endl; - return; -} - -/// Prints on the standard output the decoded HMPID error field -/// @param[in] ErrorField : the HMPID readout error field -void HmpidDecoder::dumpHmpidError(int ErrorField) -{ - char printbuf[MAXHMPIDERRORS * MAXDESCRIPTIONLENGHT]; - if (decodeHmpidError(ErrorField, printbuf) == true) { - LOG(error) << "HMPID Error field = " << ErrorField << " : " << printbuf; - } - return; -} - -/// Writes in a ASCCI File the complete report of the decoding -/// procedure -/// @param[in] *summaryFileName : the name of the output file -/// @throws TH_CREATEFILE Thrown if was not able to create the file -void HmpidDecoder::writeSummaryFile(char* summaryFileName) -{ - FILE* fs = fopen(summaryFileName, "w"); - if (fs == nullptr) { - printf("Error opening the file %s !\n", summaryFileName); - throw TH_CREATEFILE; - } - - fprintf(fs, "HMPID Readout Raw Data Decoding Summary File\n"); - fprintf(fs, "Equipment Id\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->getEquipmentId()); - } - fprintf(fs, "\n"); - - fprintf(fs, "Number of events\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mNumberOfEvents); - } - fprintf(fs, "\n"); - - fprintf(fs, "Average Event Size\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%f\t", mTheEquipments[i]->mEventSizeAverage); - } - fprintf(fs, "\n"); - - fprintf(fs, "Total pads\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mTotalPads); - } - fprintf(fs, "\n"); - - fprintf(fs, "Average pads per event\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%f\t", mTheEquipments[i]->mPadsPerEventAverage); - } - fprintf(fs, "\n"); - - fprintf(fs, "Busy Time average\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%e\t", mTheEquipments[i]->mBusyTimeAverage); - } - fprintf(fs, "\n"); - - fprintf(fs, "Event rate\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%e\t", 1 / mTheEquipments[i]->mBusyTimeAverage); - } - fprintf(fs, "\n"); - - fprintf(fs, "Number of Empty Events\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mNumberOfEmptyEvents); - } - fprintf(fs, "\n"); - - fprintf(fs, "-------------Errors--------------------\n"); - fprintf(fs, "Wrong events\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mNumberOfWrongEvents); - } - fprintf(fs, "\n"); - - for (int j = 0; j < MAXERRORS; j++) { - fprintf(fs, "%s\t", sErrorDescription[j]); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mErrors[j]); - } - fprintf(fs, "\n"); - } - - fprintf(fs, "Total errors\t"); - for (int i = 0; i < Geo::MAXEQUIPMENTS; i++) { - fprintf(fs, "%d\t", mTheEquipments[i]->mTotalErrors); - } - fprintf(fs, "\n"); - - fclose(fs); - return; -} diff --git a/Detectors/HMPID/workflow/include/HMPIDWorkflow/DigitReaderSpec.h_notused.h b/Detectors/HMPID/workflow/include/HMPIDWorkflow/DigitReaderSpec.h_notused.h deleted file mode 100644 index eea9b134bd911..0000000000000 --- a/Detectors/HMPID/workflow/include/HMPIDWorkflow/DigitReaderSpec.h_notused.h +++ /dev/null @@ -1,53 +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 DigitReader.h - -#ifndef O2_HMPID_DIGITREADER -#define O2_HMPID_DIGITREADER - -#include "TFile.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" -#include "DataFormatsHMP/Digit.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" - -namespace o2 -{ -namespace hmpid -{ - -class DigitReader : public o2::framework::Task -{ - public: - DigitReader(bool useMC) : mUseMC(useMC) {} - ~DigitReader() override = default; - void init(o2::framework::InitContext& ic) final; - void run(o2::framework::ProcessingContext& pc) final; - - private: - int mState = 0; - bool mUseMC = true; - std::unique_ptr mFile = nullptr; - - std::vector mDigits, *mPdigits = &mDigits; - - o2::dataformats::MCTruthContainer mLabels, *mPlabels = &mLabels; -}; - -/// read simulated HMPID digits from a root file -framework::DataProcessorSpec getDigitReaderSpec(bool useMC); - -} // namespace hmpid -} // namespace o2 - -#endif /* O2_HMPID_DIGITREADER */ diff --git a/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyDecoderSpec.h b/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyDecoderSpec.h index 8c64f326a6878..d03a30ab905e5 100644 --- a/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyDecoderSpec.h +++ b/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyDecoderSpec.h @@ -24,7 +24,7 @@ namespace hmpid { /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt = "none"); } // namespace hmpid } // namespace o2 diff --git a/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyEncoderSpec.h b/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyEncoderSpec.h index 2fb9fd301f13b..9c2c4eb5b4fb0 100644 --- a/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyEncoderSpec.h +++ b/Detectors/HMPID/workflow/include/HMPIDWorkflow/EntropyEncoderSpec.h @@ -24,7 +24,7 @@ namespace hmpid { /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace hmpid } // namespace o2 diff --git a/Detectors/HMPID/workflow/src/EntropyDecoderSpec.cxx b/Detectors/HMPID/workflow/src/EntropyDecoderSpec.cxx index aa22979bc305f..9ec05efc846fb 100644 --- a/Detectors/HMPID/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/HMPID/workflow/src/EntropyDecoderSpec.cxx @@ -26,11 +26,10 @@ namespace o2 { namespace hmpid { - class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -91,7 +90,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"triggers"}, "HMP", "INTRECORDS", 0, Lifetime::Timeframe}, @@ -100,17 +99,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_HMP", "HMP", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_HMP", "HMP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("HMP/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_HMP", "HMP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("HMP/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "hmpid-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace hmpid } // namespace o2 diff --git a/Detectors/HMPID/workflow/src/EntropyEncoderSpec.cxx b/Detectors/HMPID/workflow/src/EntropyEncoderSpec.cxx index 95723f42d0fd6..c29c1cee459bc 100644 --- a/Detectors/HMPID/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/HMPID/workflow/src/EntropyEncoderSpec.cxx @@ -27,11 +27,10 @@ namespace o2 { namespace hmpid { - class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR = false); + EntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -44,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -89,12 +88,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("triggers", "HMP", "INTRECORDS", 0, Lifetime::Timeframe); inputs.emplace_back("digits", "HMP", "DIGITS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "HMP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("HMP/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "HMP", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("HMP/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -103,13 +105,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"HMP", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "HMP", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace hmpid } // namespace o2 diff --git a/Detectors/HMPID/workflow/src/entropy-encoder-workflow.cxx b/Detectors/HMPID/workflow/src/entropy-encoder-workflow.cxx index fde5e0183abd6..4a1883233b605 100644 --- a/Detectors/HMPID/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/HMPID/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::hmpid::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::hmpid::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/ITSMFT/ITS/QC/TestDataReaderWorkflow/src/TestDataReader.cxx b/Detectors/ITSMFT/ITS/QC/TestDataReaderWorkflow/src/TestDataReader.cxx index 1fc4442e3bdbf..90ed033ed67da 100644 --- a/Detectors/ITSMFT/ITS/QC/TestDataReaderWorkflow/src/TestDataReader.cxx +++ b/Detectors/ITSMFT/ITS/QC/TestDataReaderWorkflow/src/TestDataReader.cxx @@ -509,7 +509,6 @@ void TestDataReader::run(ProcessingContext& pc) std::vector TestDataReader::GetFName(std::string folder) { - DIR* dirp; char cstr[folder.size() + 1]; diff --git a/Detectors/ITSMFT/ITS/base/include/ITSBase/GeometryTGeo.h b/Detectors/ITSMFT/ITS/base/include/ITSBase/GeometryTGeo.h index fcdc978fa64f0..c8ef445e273d3 100644 --- a/Detectors/ITSMFT/ITS/base/include/ITSBase/GeometryTGeo.h +++ b/Detectors/ITSMFT/ITS/base/include/ITSBase/GeometryTGeo.h @@ -176,7 +176,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo bool getChipId(int index, int& lay, int& hba, int& sta, int& ssta, int& mod, int& chip) const; /// Get chip layer, from 0 - int getLayer(int index) const; + int getLayer(int index) const final; /// Get chip half barrel, from 0 int getHalfBarrel(int index) const; @@ -216,7 +216,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo return getSymbolicName(getChipIndex(lay, hba, sta, det)); } - /// Get the transformation matrix for a given chip (NOT A SENSOR!!!) 'index' by quering the TGeoManager + /// Get the transformation matrix for a given chip (NOT A SENSOR!!!) 'index' by querying the TGeoManager TGeoHMatrix* getMatrix(int index) const { return o2::base::GeometryManager::getMatrix(getDetID(), index); } TGeoHMatrix* getMatrix(int lay, int hba, int sta, int sens) const { return getMatrix(getChipIndex(lay, hba, sta, sens)); } bool getOriginalMatrix(int index, TGeoHMatrix& m) const @@ -314,7 +314,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static const char* getITS3PixelArrayPattern(int layer) { return Form("%s%d", getITS3PixelArrayPatternRaw(), layer); }; /// sym name of the layer - static const char* composeSymNameITS(bool isITS3 = false); + static const char* composeSymNameITS(); /// sym name of the layer static const char* composeSymNameLayer(int lr, bool isITS3 = false); @@ -333,13 +333,10 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo /// Sym name of the chip in the given layer/halfbarrel/stave/substave/module static const char* composeSymNameChip(int lr, int hba, int sta, int ssta, int mod, int chip, bool isITS3 = false); - // create matrix for transformation from tracking frame to local one for ITS3 - const Mat3D getT2LMatrixITS3(int isn, float alpha); - TString getMatrixPath(int index) const; /// Get the transformation matrix of the SENSOR (not necessary the same as the chip) - /// for a given chip 'index' by quering the TGeoManager + /// for a given chip 'index' by querying the TGeoManager TGeoHMatrix* extractMatrixSensor(int index) const; // create matrix for transformation from sensor local frame to global one @@ -410,7 +407,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo std::vector mNumberOfChipsPerStave; ///< number of chips per stave std::vector mNumberOfChipsPerHalfBarrel; ///< number of chips per halfbarrel std::vector mNumberOfChipsPerLayer; ///< number of chips per stave - std::vector mLastChipIndex; ///< max ID of the detctor in the layer + std::vector mLastChipIndex; ///< max ID of the detector in the layer std::array mIsLayerITS3; ///< flag with the information of the ITS version (ITS2 or ITS3) std::array mLayerToWrapper; ///< Layer to wrapper correspondence diff --git a/Detectors/ITSMFT/ITS/base/src/GeometryTGeo.cxx b/Detectors/ITSMFT/ITS/base/src/GeometryTGeo.cxx index 89b4d63729543..5dc499d05037e 100644 --- a/Detectors/ITSMFT/ITS/base/src/GeometryTGeo.cxx +++ b/Detectors/ITSMFT/ITS/base/src/GeometryTGeo.cxx @@ -290,14 +290,8 @@ bool GeometryTGeo::getChipId(int index, int& lay, int& hba, int& sta, int& hsta, } //__________________________________________________________________________ -const char* GeometryTGeo::composeSymNameITS(bool isITS3) +const char* GeometryTGeo::composeSymNameITS() { - if (isITS3) { -#ifdef ENABLE_UPGRADES - return o2::detectors::DetID(o2::detectors::DetID::IT3).getName(); -#endif - } - return o2::detectors::DetID(o2::detectors::DetID::ITS).getName(); } @@ -899,19 +893,6 @@ TGeoHMatrix& GeometryTGeo::createT2LMatrix(int isn) return t2l; } -//__________________________________________________________________________ -const o2::math_utils::Transform3D GeometryTGeo::getT2LMatrixITS3(int isn, float alpha) -{ - // create for sensor isn the TGeo matrix for Tracking to Local frame transformations with correction for effective thickness - static TGeoHMatrix t2l; - t2l.Clear(); - t2l.RotateZ(alpha * RadToDeg()); // rotate in direction of normal to the tangent to the cylinder - const TGeoHMatrix& matL2G = getMatrixL2G(isn); - const auto& matL2Gi = matL2G.Inverse(); - t2l.MultiplyLeft(&matL2Gi); - return Mat3D(t2l); -} - //__________________________________________________________________________ int GeometryTGeo::extractVolumeCopy(const char* name, const char* prefix) const { 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/macros/test/CheckTracksCA.C b/Detectors/ITSMFT/ITS/macros/test/CheckTracksCA.C index 7c128ce34d538..e185be83a389f 100644 --- a/Detectors/ITSMFT/ITS/macros/test/CheckTracksCA.C +++ b/Detectors/ITSMFT/ITS/macros/test/CheckTracksCA.C @@ -21,16 +21,21 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include "ITSBase/GeometryTGeo.h" #include "SimulationDataFormat/MCEventHeader.h" #include "DetectorsBase/Propagator.h" #include "SimulationDataFormat/TrackReference.h" +#include "SimulationDataFormat/O2DatabasePDG.h" #include "SimulationDataFormat/MCTrack.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" @@ -41,6 +46,19 @@ using namespace std; +// chi2 PDF with amplitude A, degrees of freedom k, scale s +Double_t chi2_pdf(Double_t* x, Double_t* par) +{ + const Double_t xx = x[0]; + const Double_t A = par[0]; + const Double_t k = par[1]; + const Double_t s = par[2]; + if (xx <= 0.0 || k <= 0.0 || s <= 0.0) + return 0.0; + const Double_t coef = 1.0 / (TMath::Power(2.0 * s, k * 0.5) * TMath::Gamma(k * 0.5)); + return A * coef * TMath::Power(xx, k * 0.5 - 1.0) * TMath::Exp(-xx / (2.0 * s)); +} + struct ParticleInfo { int event; int pdg; @@ -60,11 +78,15 @@ struct ParticleInfo { bool isPrimary = 0u; unsigned char storedStatus = 2; /// not stored = 2, fake = 1, good = 0 o2::its::TrackITS track; + o2::MCTrack mcTrack; }; #pragma link C++ class ParticleInfo + ; -void CheckTracksCA(bool doFakeClStud = false, +void CheckTracksCA(bool doEffStud = true, + bool doFakeClStud = false, + bool doPullStud = false, + bool createOutput = false, std::string tracfile = "o2trac_its.root", std::string magfile = "o2sim_grp.root", std::string clusfile = "o2clus_its.root", @@ -124,7 +146,10 @@ void CheckTracksCA(bool doFakeClStud = false, info[n].resize(mcArr->size()); hZvertex->Fill(mcEvent->GetZ()); for (unsigned int mcI{0}; mcI < mcArr->size(); ++mcI) { - auto part = mcArr->at(mcI); + const auto part = mcArr->at(mcI); + if (!o2::O2DatabasePDG::Instance()->GetParticle(part.GetPdgCode())) { + continue; + } info[n][mcI].event = n; info[n][mcI].pdg = part.GetPdgCode(); info[n][mcI].pvx = mcEvent->GetX(); @@ -134,6 +159,7 @@ void CheckTracksCA(bool doFakeClStud = false, info[n][mcI].phi = part.GetPhi(); info[n][mcI].eta = part.GetEta(); info[n][mcI].isPrimary = part.isPrimary(); + info[n][mcI].mcTrack = part; } } std::cout << "done." << std::endl; @@ -214,117 +240,108 @@ void CheckTracksCA(bool doFakeClStud = false, std::cout << "\t- Total number of fakes: " << fakes << " (" << fakes * 100. / total << "%)" << std::endl; std::cout << "\t- Total number of good: " << good << " (" << good * 100. / total << "%)" << std::endl; - int nb = 100; - double xbins[nb + 1], ptcutl = 0.01, ptcuth = 10.; - double a = std::log(ptcuth / ptcutl) / nb; - for (int i = 0; i <= nb; i++) - xbins[i] = ptcutl * std::exp(i * a); - TH1D* num = new TH1D("num", ";#it{p}_{T} (GeV/#it{c});Efficiency (fake-track rate)", nb, xbins); - num->Sumw2(); - TH1D* numEta = new TH1D("numEta", ";#eta;Number of tracks", 60, -3, 3); - numEta->Sumw2(); - TH1D* numChi2 = new TH1D("numChi2", ";#it{p}_{T} (GeV/#it{c});Efficiency (fake-track rate)", 200, 0, 100); - - TH1D* fak = new TH1D("fak", ";#it{p}_{T} (GeV/#it{c});Fak", nb, xbins); - fak->Sumw2(); - TH1D* multiFak = new TH1D("multiFak", ";#it{p}_{T} (GeV/#it{c});Fak", nb, xbins); - multiFak->Sumw2(); - TH1D* fakChi2 = new TH1D("fakChi2", ";#it{p}_{T} (GeV/#it{c});Fak", 200, 0, 100); - - TH1D* clone = new TH1D("clone", ";#it{p}_{T} (GeV/#it{c});Clone", nb, xbins); - clone->Sumw2(); - - TH1D* den = new TH1D("den", ";#it{p}_{T} (GeV/#it{c});Den", nb, xbins); - den->Sumw2(); - - for (auto& evInfo : info) { - for (auto& part : evInfo) { - if ((part.clusters & 0x7f) != 0x7f) { - // part.clusters != 0x3f && part.clusters != 0x3f << 1 && - // part.clusters != 0x1f && part.clusters != 0x1f << 1 && part.clusters != 0x1f << 2 && - // part.clusters != 0x0f && part.clusters != 0x0f << 1 && part.clusters != 0x0f << 2 && part.clusters != 0x0f << 3) { - continue; - } - if (!part.isPrimary) { - continue; - } - den->Fill(part.pt); - if (part.isReco) { - num->Fill(part.pt); - numEta->Fill(part.eta); - if (part.isReco > 1) { - for (int _i{0}; _i < part.isReco - 1; ++_i) { - clone->Fill(part.pt); + TFile* file{nullptr}; + if (createOutput) { + file = TFile::Open("CheckTracksCA.root", "recreate"); + } + + if (doEffStud) { + std::cout << "Calculating efficiencies... "; + const int nb = 100; + double xbins[nb + 1], ptcutl = 0.01, ptcuth = 10.; + double a = std::log(ptcuth / ptcutl) / nb; + for (int i = 0; i <= nb; i++) + xbins[i] = ptcutl * std::exp(i * a); + TH1D* num = new TH1D("num", ";#it{p}_{T} (GeV/#it{c});Efficiency (fake-track rate)", nb, xbins); + num->Sumw2(); + TH1D* numEta = new TH1D("numEta", ";#eta;Number of tracks", 60, -3, 3); + numEta->Sumw2(); + TH1D* numChi2 = new TH1D("numChi2", ";#it{p}_{T} (GeV/#it{c});Efficiency (fake-track rate)", 200, 0, 100); + + TH1D* fak = new TH1D("fak", ";#it{p}_{T} (GeV/#it{c});Fak", nb, xbins); + fak->Sumw2(); + TH1D* multiFak = new TH1D("multiFak", ";#it{p}_{T} (GeV/#it{c});Fak", nb, xbins); + multiFak->Sumw2(); + TH1D* fakChi2 = new TH1D("fakChi2", ";#it{p}_{T} (GeV/#it{c});Fak", 200, 0, 100); + + TH1D* clone = new TH1D("clone", ";#it{p}_{T} (GeV/#it{c});Clone", nb, xbins); + clone->Sumw2(); + + TH1D* den = new TH1D("den", ";#it{p}_{T} (GeV/#it{c});Den", nb, xbins); + den->Sumw2(); + + for (auto& evInfo : info) { + for (auto& part : evInfo) { + if ((part.clusters & 0x7f) != 0x7f) { + // part.clusters != 0x3f && part.clusters != 0x3f << 1 && + // part.clusters != 0x1f && part.clusters != 0x1f << 1 && part.clusters != 0x1f << 2 && + // part.clusters != 0x0f && part.clusters != 0x0f << 1 && part.clusters != 0x0f << 2 && part.clusters != 0x0f << 3) { + continue; + } + if (!part.isPrimary) { + continue; + } + den->Fill(part.pt); + if (part.isReco) { + num->Fill(part.pt); + numEta->Fill(part.eta); + if (part.isReco > 1) { + for (int _i{0}; _i < part.isReco - 1; ++_i) { + clone->Fill(part.pt); + } } } - } - if (part.isFake) { - fak->Fill(part.pt); - if (part.isFake > 1) { - for (int _i{0}; _i < part.isFake - 1; ++_i) { - multiFak->Fill(part.pt); + if (part.isFake) { + fak->Fill(part.pt); + if (part.isFake > 1) { + for (int _i{0}; _i < part.isFake - 1; ++_i) { + multiFak->Fill(part.pt); + } } } } } - } - TCanvas* c1 = new TCanvas; - c1->SetLogx(); - c1->SetGridx(); - c1->SetGridy(); - TH1* sum = (TH1*)num->Clone("sum"); - sum->Add(fak); - sum->Divide(sum, den, 1, 1); - sum->SetLineColor(kBlack); - sum->Draw("hist"); - num->Divide(num, den, 1, 1, "b"); - num->Draw("histesame"); - fak->Divide(fak, den, 1, 1, "b"); - fak->SetLineColor(2); - fak->Draw("histesame"); - multiFak->Divide(multiFak, den, 1, 1, "b"); - multiFak->SetLineColor(kRed + 1); - multiFak->Draw("histsame"); - clone->Divide(clone, den, 1, 1, "b"); - clone->SetLineColor(3); - clone->Draw("histesame"); - TCanvas* c2 = new TCanvas; - c2->SetGridx(); - c2->SetGridy(); - hZvertex->DrawClone(); - - std::cout << "** Streaming output TTree to file ... " << std::flush; - TFile file("CheckTracksCA.root", "recreate"); - TTree tree("ParticleInfo", "ParticleInfo"); - ParticleInfo pInfo; - tree.Branch("particle", &pInfo); - for (auto& event : info) { - for (auto& part : event) { - int nCl{0}; - for (unsigned int bit{0}; bit < sizeof(pInfo.clusters) * 8; ++bit) { - nCl += bool(part.clusters & (1 << bit)); - } - if (nCl < 3) { - continue; - } - pInfo = part; - tree.Fill(); + TCanvas* c1 = new TCanvas; + c1->SetLogx(); + c1->SetGridx(); + c1->SetGridy(); + TH1* sum = (TH1*)num->Clone("sum"); + sum->Add(fak); + sum->Divide(sum, den, 1, 1); + sum->SetLineColor(kBlack); + sum->Draw("hist"); + num->Divide(num, den, 1, 1, "b"); + num->Draw("histesame"); + fak->Divide(fak, den, 1, 1, "b"); + fak->SetLineColor(2); + fak->Draw("histesame"); + multiFak->Divide(multiFak, den, 1, 1, "b"); + multiFak->SetLineColor(kRed + 1); + multiFak->Draw("histsame"); + clone->Divide(clone, den, 1, 1, "b"); + clone->SetLineColor(3); + clone->Draw("histesame"); + TCanvas* c2 = new TCanvas; + c2->SetGridx(); + c2->SetGridy(); + hZvertex->DrawClone(); + + if (createOutput) { + sum->Write("total"); + fak->Write("singleFake"); + num->Write("efficiency"); + numEta->Write("etaDist"); + multiFak->Write("multiFake"); + clone->Write("clones"); } + std::cout << " done." << std::endl; } - tree.Write(); - sum->Write("total"); - fak->Write("singleFake"); - num->Write("efficiency"); - numEta->Write("etaDist"); - multiFak->Write("multiFake"); - clone->Write("clones"); - file.Close(); - std::cout << " done." << std::endl; ////////////////////// // Fake clusters study if (doFakeClStud) { + std::cout << "Creating fake cluster study... "; std::vector histLength, histLength1Fake, histLengthNoCl, histLength1FakeNoCl; std::vector stackLength, stackLength1Fake; std::vector legends, legends1Fake; @@ -364,10 +381,10 @@ void CheckTracksCA(bool doFakeClStud = false, stackLength1Fake[iH - 4]->Add(histLength1FakeNoCl[iH - 4]); } - for (auto& event : info) { - for (auto& part : event) { + for (const auto& event : info) { + for (const auto& part : event) { int nCl{0}; - for (unsigned int bit{0}; bit < sizeof(pInfo.clusters) * 8; ++bit) { + for (unsigned int bit{0}; bit < sizeof(part.clusters) * 8; ++bit) { nCl += bool(part.clusters & (1 << bit)); } if (nCl < 3) { @@ -409,5 +426,237 @@ void CheckTracksCA(bool doFakeClStud = false, gPad->BuildLegend(); } canvas->SaveAs("fakeClusters.png", "recreate"); + std::cout << " done\n"; + } + + if (doPullStud) { + std::cout << "Creating pull study... "; + const int nBins{30}; + const float xWidth{10}; + // Pulls + auto hYPull = new TH1F("hYPull", "Pull Y", nBins, -xWidth, xWidth); + auto hZPull = new TH1F("hZPull", "Pull Z", nBins, -xWidth, xWidth); + auto hSPhiPull = new TH1F("hSPhiPull", "Pull Sin(Phi)", nBins, -xWidth, xWidth); + auto hTglPull = new TH1F("hTglPull", "Pull Tg(Lambda)", nBins, -xWidth, xWidth); + auto hQoPtPull = new TH1F("hQoPtPull", "Pull Q/Pt", nBins, -xWidth, xWidth); + // Correlation + float maxY2{1e-6}, maxZ2{1e-6}, maxSnp2{2e-6}, maxTgl2{2e-6}, max1Pt2{0.01}; + auto hCorYZ = new TH2F("hCorYZ", ";#sigma_{Z}^{2};#sigma_{Y}^{2}", nBins, 0, maxZ2, nBins, 0, maxY2); + auto hCorYSPhi = new TH2F("hCorYSPhi", ";#sigma_{snp}^{2};#sigma_{Y}^{2}", nBins, 0, maxSnp2, nBins, 0, maxY2); + auto hCorYTgl = new TH2F("hCorYTgl", ";#sigma_{tgl}^{2};#sigma_{Y}^{2}", nBins, 0, maxTgl2, nBins, 0, maxY2); + auto hCorYQoPt = new TH2F("hCorYQoPt", ";#sigma_{Q/Pt}^{2};#sigma_{Y}^{2}", nBins, 0, max1Pt2, nBins, 0, maxY2); + + auto hCorZSPhi = new TH2F("hCorZSPhi", ";#sigma_{snp}^{2};#sigma_{Z}^{2}", nBins, 0, maxSnp2, nBins, 0, maxZ2); + auto hCorZTgl = new TH2F("hCorZTgl", ";#sigma_{tgl}^{2};#sigma_{Z}^{2}", nBins, 0, maxTgl2, nBins, 0, maxZ2); + auto hCorZQoPt = new TH2F("hCorZQoPt", ";#sigma_{Q/Pt}^{2};#sigma_{Z}^{2}", nBins, 0, max1Pt2, nBins, 0, maxZ2); + + auto hCorSPhiTgl = new TH2F("hCorSPhiTgl", ";#sigma_{tgl}^{2};#sigma_{snp}^{2}", nBins, 0, maxTgl2, nBins, 0, maxSnp2); + auto hCorSPhiQoPt = new TH2F("hCorSPhiQoPt", ";#sigma_{Q/Pt}^{2};#sigma_{snp}^{2}", nBins, 0, max1Pt2, nBins, 0, maxSnp2); + + auto hCorTglQoPt = new TH2F("hCorTglQoPt", ";#sigma_{Q/Pt}^{2};#sigma_{tgl}^{2}", nBins, 0, max1Pt2, nBins, 0, maxTgl2); + + auto calcMahalanobisDist = [&](const auto& trk, const auto& mc) -> float { + o2::math_utils::SMatrix> cov; + cov(o2::track::kY, o2::track::kY) = trk.getSigmaY2(); + cov(o2::track::kZ, o2::track::kY) = trk.getSigmaZY(); + cov(o2::track::kZ, o2::track::kZ) = trk.getSigmaZ2(); + cov(o2::track::kSnp, o2::track::kY) = trk.getSigmaSnpY(); + cov(o2::track::kSnp, o2::track::kZ) = trk.getSigmaSnpZ(); + cov(o2::track::kSnp, o2::track::kSnp) = trk.getSigmaSnp2(); + cov(o2::track::kTgl, o2::track::kY) = trk.getSigmaTglY(); + cov(o2::track::kTgl, o2::track::kZ) = trk.getSigmaTglZ(); + cov(o2::track::kTgl, o2::track::kSnp) = trk.getSigmaTglSnp(); + cov(o2::track::kTgl, o2::track::kTgl) = trk.getSigmaTgl2(); + cov(o2::track::kQ2Pt, o2::track::kY) = trk.getSigma1PtY(); + cov(o2::track::kQ2Pt, o2::track::kZ) = trk.getSigma1PtZ(); + cov(o2::track::kQ2Pt, o2::track::kSnp) = trk.getSigma1PtSnp(); + cov(o2::track::kQ2Pt, o2::track::kTgl) = trk.getSigma1PtTgl(); + cov(o2::track::kQ2Pt, o2::track::kQ2Pt) = trk.getSigma1Pt2(); + if (!cov.Invert()) { + return -1.f; + } + o2::math_utils::SVector trkPar(trk.getParams(), o2::track::kNParams), mcPar(mc.getParams(), o2::track::kNParams); + auto res = trkPar - mcPar; + return std::sqrt(ROOT::Math::Similarity(cov, res)); + }; + auto hMahDist = new TH1F("hMahDist", ";Mahalanobis distance;n. entries", 100, 0, 10); + TF1* fchi = new TF1("fchi", chi2_pdf, 0, 6, 3); + fchi->SetParNames("A", "k", "s"); + fchi->SetParameter(0, 1); + fchi->SetParameter(1, 5); + fchi->SetParameter(2, 1); + + for (const auto& event : info) { + for (const auto& part : event) { + if (((part.clusters & 0x7f) != 0x7f) && !part.isPrimary) { + continue; + } + + // prepare mc truth parameters + std::array xyz{(float)part.mcTrack.GetStartVertexCoordinatesX(), (float)part.mcTrack.GetStartVertexCoordinatesY(), (float)part.mcTrack.GetStartVertexCoordinatesZ()}; + std::array pxyz{(float)part.mcTrack.GetStartVertexMomentumX(), (float)part.mcTrack.GetStartVertexMomentumY(), (float)part.mcTrack.GetStartVertexMomentumZ()}; + o2::track::TrackPar mcTrack(xyz, pxyz, TMath::Nint(o2::O2DatabasePDG::Instance()->GetParticle(part.mcTrack.GetPdgCode())->Charge() / 3), false); + if (!mcTrack.rotate(part.track.getAlpha()) || + !o2::base::Propagator::Instance()->propagateTo(mcTrack, part.track.getX())) { + continue; + } + + const float sY = part.track.getSigmaY2(); + const float sZ = part.track.getSigmaZ2(); + const float sSnp = part.track.getSigmaSnp2(); + const float sTgl = part.track.getSigmaTgl2(); + const float s1Pt = part.track.getSigma1Pt2(); + + hYPull->Fill((part.track.getY() - mcTrack.getY()) / std::sqrt(part.track.getSigmaY2())); + hZPull->Fill((part.track.getZ() - mcTrack.getZ()) / std::sqrt(part.track.getSigmaZ2())); + hSPhiPull->Fill((part.track.getSnp() - mcTrack.getSnp()) / std::sqrt(part.track.getSigmaSnp2())); + hTglPull->Fill((part.track.getTgl() - mcTrack.getTgl()) / std::sqrt(part.track.getSigmaTgl2())); + hQoPtPull->Fill((part.track.getQ2Pt() - mcTrack.getQ2Pt()) / std::sqrt(part.track.getSigma1Pt2())); + + hCorYZ->Fill(part.track.getSigmaZ2(), part.track.getSigmaY2()); + hCorYSPhi->Fill(part.track.getSigmaSnp2(), part.track.getSigmaY2()); + hCorYTgl->Fill(part.track.getSigmaTgl2(), part.track.getSigmaY2()); + hCorYQoPt->Fill(part.track.getSigma1Pt2(), part.track.getSigmaY2()); + + hCorZSPhi->Fill(part.track.getSigmaSnp2(), part.track.getSigmaZ2()); + hCorZTgl->Fill(part.track.getSigmaTgl2(), part.track.getSigmaZ2()); + hCorZQoPt->Fill(part.track.getSigma1Pt2(), part.track.getSigmaZ2()); + + hCorSPhiTgl->Fill(part.track.getSigmaTgl2(), part.track.getSigmaSnp2()); + hCorSPhiQoPt->Fill(part.track.getSigma1Pt2(), part.track.getSigmaSnp2()); + + hCorTglQoPt->Fill(part.track.getSigma1Pt2(), part.track.getSigmaTgl2()); + + hMahDist->Fill(calcMahalanobisDist(part.track, mcTrack)); + } + } + + // normalise, set axis, fit and draw + auto doPullCalc = [](TH1F* h) { + h->Scale(1. / h->Integral("width")); + h->GetYaxis()->SetRangeUser(1e-5, 1.); + gPad->SetLogy(); + h->Draw("hist"); + h->Fit("gaus", "QMR", "", -3, 3); + if (auto f = h->GetFunction("gaus")) { + f->SetLineColor(kRed); + f->SetLineWidth(2); + f->Draw("same"); + const double mean = f->GetParameter(1); + const double sigma = f->GetParameter(2); + TLatex lat; + lat.SetNDC(); + lat.SetTextFont(42); + lat.SetTextSize(0.04); + lat.DrawLatex(0.62, 0.85, Form("#mu = %.4f", mean)); + lat.DrawLatex(0.62, 0.79, Form("#sigma = %.4f", sigma)); + } + }; + hMahDist->Scale(1. / hMahDist->Integral("width")); + TFitResultPtr fitres = hMahDist->Fit(fchi, "RMQS"); + + auto c = new TCanvas("cPull", "", 2000, 1000); + c->Divide(5, 5); + c->cd(1); + doPullCalc(hYPull); + c->cd(2); + hCorYZ->Draw("colz"); + c->cd(3); + hCorYSPhi->Draw("colz"); + c->cd(4); + hCorYTgl->Draw("colz"); + c->cd(5); + hCorYQoPt->Draw("colz"); + + c->cd(7); + doPullCalc(hZPull); + c->cd(8); + hCorZSPhi->Draw("colz"); + c->cd(9); + hCorZTgl->Draw("colz"); + c->cd(10); + hCorZQoPt->Draw("colz"); + + c->cd(13); + doPullCalc(hSPhiPull); + c->cd(14); + hCorSPhiTgl->Draw("colz"); + c->cd(15); + hCorSPhiQoPt->Draw("colz"); + + c->cd(19); + doPullCalc(hTglPull); + c->cd(20); + hCorTglQoPt->Draw("colz"); + + c->cd(); + const double xlow = 0.0; + const double xup = 0.4; + const double ylow = 0.0; + const double yup = 0.4; + auto pMahBig = new TPad("pMahBig", "Mahalanobis Distance", xlow, ylow, xup, yup); + pMahBig->SetFillStyle(4000); + pMahBig->SetBorderMode(0); + pMahBig->SetLeftMargin(0.12); + pMahBig->SetRightMargin(0.02); + pMahBig->SetBottomMargin(0.12); + pMahBig->SetTopMargin(0.05); + pMahBig->Draw(); + pMahBig->cd(); + hMahDist->Draw("hist"); + fchi->SetLineColor(kRed); + fchi->SetLineWidth(2); + fchi->Draw("same"); + const Double_t A_fit = fchi->GetParameter(0); + const Double_t k_fit = fchi->GetParameter(1); + const Double_t s_fit = fchi->GetParameter(2); + const Double_t A_err = fchi->GetParError(0); + const Double_t k_err = fchi->GetParError(1); + const Double_t s_err = fchi->GetParError(2); + const Double_t chi2 = fchi->GetChisquare(); + const Int_t ndf = fchi->GetNDF(); + TLatex lat; + lat.SetNDC(); + lat.SetTextFont(42); + lat.SetTextSize(0.038); + lat.SetTextAlign(11); + const Double_t xText = 0.55; + Double_t yText = 0.85; + const Double_t dy = 0.06; + lat.DrawLatex(xText, yText, Form("A = %.3g #pm %.3g", A_fit, A_err)); + yText -= dy; + lat.DrawLatex(xText, yText, Form("k (ndf) = %.3f #pm %.3f", k_fit, k_err)); + yText -= dy; + lat.DrawLatex(xText, yText, Form("scale s = %.3g #pm %.3g", s_fit, s_err)); + yText -= dy; + lat.DrawLatex(xText, yText, Form("#chi^{2}/ndf = %.2f / %d", chi2, ndf)); + yText -= dy; + if (fitres.Get()) { + lat.DrawLatex(xText, yText, Form("Fit status = %d", fitres->Status())); + yText -= dy; + } + + c->cd(25); + doPullCalc(hQoPtPull); + c->Draw(); + std::cout << " done\n"; + } + + if (createOutput) { + std::cout << "** Streaming output TTree to file ... " << std::flush; + TTree tree("ParticleInfo", "ParticleInfo"); + ParticleInfo pInfo; + tree.Branch("particle", &pInfo); + for (const auto& event : info) { + for (const auto& part : event) { + if (((part.clusters & 0x7f) != 0x7f) && !part.isPrimary) { + continue; + } + pInfo = part; + tree.Fill(); + } + } + tree.Write(); + file->Close(); } } 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/HMPID/workflow/include/HMPIDWorkflow/ClusterizerSpec.h_notused.h b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h similarity index 62% rename from Detectors/HMPID/workflow/include/HMPIDWorkflow/ClusterizerSpec.h_notused.h rename to Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h index 6102ec481c97c..cd96a9df8dee6 100644 --- a/Detectors/HMPID/workflow/include/HMPIDWorkflow/ClusterizerSpec.h_notused.h +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/include/ITSStudies/ITSBeamBackgroundStudy.h @@ -9,19 +9,18 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef STEER_DIGITIZERWORKFLOW_HMPIDCLUSTERIZER_H_ -#define STEER_DIGITIZERWORKFLOW_HMPIDCLUSTERIZER_H_ +#ifndef O2_ITS_BEAMBKG_STUDY_H +#define O2_ITS_BEAMBKG_STUDY_H #include "Framework/DataProcessorSpec.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" -namespace o2 -{ -namespace hmpid +namespace o2::its::study { -o2::framework::DataProcessorSpec getHMPIDClusterizerSpec(bool useMC); +using mask_t = o2::dataformats::GlobalTrackID::mask_t; -} // end namespace hmpid -} // end namespace o2 +o2::framework::DataProcessorSpec getITSBeamBackgroundStudy(mask_t srcTracksMask, mask_t srcClustersMask, bool useMC); -#endif /* STEER_DIGITIZERWORKFLOW_HMPIDCLUSTERIZERSPEC_H_ */ +} // 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/studies/src/PIDStudy.cxx b/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx index 4b0f553eb774b..9a7f6c218cd12 100644 --- a/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx +++ b/Detectors/ITSMFT/ITS/postprocessing/studies/src/PIDStudy.cxx @@ -91,7 +91,7 @@ class PIDStudy : public Task std::shared_ptr gr, bool isMC, std::shared_ptr kineReader) : mDataRequest{dr}, mGGCCDBRequest(gr), mUseMC(isMC), mKineReader(kineReader){}; - ~PIDStudy() final = default; + ~PIDStudy() override = default; void init(InitContext& ic) final; void run(ProcessingContext&) final; void endOfStream(EndOfStreamContext&) final; 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 3e1544c65b9de..be42015b95795 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/reconstruction/CMakeLists.txt @@ -10,12 +10,7 @@ # or submit itself to any jurisdiction. o2_add_library(ITSReconstruction - SOURCES src/ClustererTask.cxx - src/CookedTracker.cxx - src/CookedConfigParam.cxx - src/RecoGeomHelper.cxx - src/FastMultEstConfig.cxx - src/FastMultEst.cxx + SOURCES src/RecoGeomHelper.cxx PUBLIC_LINK_LIBRARIES O2::ITSBase O2::ITSMFTReconstruction O2::DataFormatsITS @@ -23,10 +18,4 @@ o2_add_library(ITSReconstruction o2_target_root_dictionary( ITSReconstruction - HEADERS include/ITSReconstruction/ClustererTask.h - include/ITSReconstruction/CookedTracker.h - include/ITSReconstruction/CookedConfigParam.h - include/ITSReconstruction/RecoGeomHelper.h - include/ITSReconstruction/FastMultEst.h - include/ITSReconstruction/FastMultEstConfig.h - LINKDEF src/CookedTrackerLinkDef.h) + HEADERS include/ITSReconstruction/RecoGeomHelper.h) diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/ClustererTask.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/ClustererTask.h deleted file mode 100644 index 16ac9dd63c631..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/ClustererTask.h +++ /dev/null @@ -1,85 +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 ClustererTask.h -/// \brief Definition of the ITS cluster finder task - -#ifndef ALICEO2_ITS_CLUSTERERTASK -#define ALICEO2_ITS_CLUSTERERTASK - -#include "ITSMFTReconstruction/ChipMappingITS.h" -#include "ITSMFTReconstruction/PixelReader.h" -#include "ITSMFTReconstruction/RawPixelReader.h" -#include "ITSMFTReconstruction/DigitPixelReader.h" -#include "ITSMFTReconstruction/Clusterer.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include -#include - -namespace o2 -{ -class MCCompLabel; -namespace dataformats -{ -template -class MCTruthContainer; -} - -namespace its -{ - -class ClustererTask -{ - using Clusterer = o2::itsmft::Clusterer; - using CompCluster = o2::itsmft::CompCluster; - using CompClusterExt = o2::itsmft::CompClusterExt; - using MCTruth = o2::dataformats::MCTruthContainer; - - public: - ClustererTask(bool useMC = true, bool raw = false); - ~ClustererTask(); - - void Init(); - Clusterer& getClusterer() { return mClusterer; } - void run(const std::string inpName, const std::string outName); - o2::itsmft::PixelReader* getReader() const { return (o2::itsmft::PixelReader*)mReader; } - - void writeTree(std::string basename, int i); - void setMaxROframe(int max) { maxROframe = max; } - int getMaxROframe() const { return maxROframe; } - - private: - int maxROframe = std::numeric_limits::max(); ///< maximal number of RO frames per a file - bool mRawDataMode = false; ///< input from raw data or MC digits - bool mUseMCTruth = true; ///< flag to use MCtruth if available - o2::itsmft::PixelReader* mReader = nullptr; ///< Pointer on the relevant Pixel reader - std::unique_ptr mReaderMC; ///< reader for MC data - std::unique_ptr> mReaderRaw; ///< reader for raw data - - Clusterer mClusterer; ///< Cluster finder - - std::vector mCompClus; //!< vector of compact clusters - - std::vector mROFRecVec; //!< vector of ROFRecord references - - MCTruth mClsLabels; //! MC labels - - std::vector mPatterns; - - ClassDefNV(ClustererTask, 2); -}; -} // namespace its -} // namespace o2 - -#endif /* ALICEO2_ITS_CLUSTERERTASK */ diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedConfigParam.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedConfigParam.h deleted file mode 100644 index bfc111d0a3803..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedConfigParam.h +++ /dev/null @@ -1,42 +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 ALICEO2_COOKEDTRACKINGPARAM_H_ -#define ALICEO2_COOKEDTRACKINGPARAM_H_ - -#include "CommonUtils/ConfigurableParamHelper.h" - -namespace o2 -{ -namespace its -{ - -struct CookedConfigParam : public o2::conf::ConfigurableParamHelper { - // seed "windows" in z and phi: makeSeeds - float zWin = 0.33; - float minPt = 0.05; - // Maximal accepted impact parameters for the seeds - float maxDCAxy = 3.; - float maxDCAz = 3.; - // Space-point resolution - float sigma = 0.0005; - // Tracking "road" from layer to layer - float roadY = 0.2; - float roadZ = 0.3; - // Minimal number of attached clusters - int minNumberOfClusters = 4; - - O2ParamDef(CookedConfigParam, "ITSCookedTracker"); -}; - -} // namespace its -} // namespace o2 -#endif diff --git a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedTracker.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedTracker.h deleted file mode 100644 index 918f7f82cbff8..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/CookedTracker.h +++ /dev/null @@ -1,267 +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 CookedTracker.h -/// \brief Definition of the "Cooked Matrix" ITS tracker -/// \author iouri.belikov@cern.ch - -#ifndef ALICEO2_ITS_COOKEDTRACKER_H -#define ALICEO2_ITS_COOKEDTRACKER_H - -//------------------------------------------------------------------------- -// A stand-alone ITS tracker -// The pattern recongintion based on the "cooked covariance" approach -//------------------------------------------------------------------------- - -#include -#include -#include "ITSBase/GeometryTGeo.h" -#include "MathUtils/Cartesian.h" -#include "DataFormatsITSMFT/Cluster.h" -#include "DataFormatsITS/TrackITS.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "ReconstructionDataFormats/Vertex.h" -#include "ITSReconstruction/CookedConfigParam.h" - -using Point3Df = o2::math_utils::Point3D; - -namespace o2 -{ -class MCCompLabel; -namespace dataformats -{ -template -class MCTruthContainer; -} -namespace itsmft -{ -class TopologyDictionary; -class CompClusterExt; -} // namespace itsmft -namespace its -{ -class CookedTracker -{ - using Cluster = o2::itsmft::Cluster; - using CompClusterExt = o2::itsmft::CompClusterExt; - using Vertex = o2::dataformats::Vertex>; - - public: - CookedTracker(Int_t nThreads = 1); - CookedTracker(const CookedTracker&) = delete; - CookedTracker& operator=(const CookedTracker& tr) = delete; - ~CookedTracker() = default; - - void setConfigParams() - { - const auto& par = CookedConfigParam::Instance(); - LOG(info) << " Setting configurable parameters..."; - - gzWin = par.zWin; - gminPt = par.minPt; - gmaxDCAxy = par.maxDCAxy; - gmaxDCAz = par.maxDCAz; - gSigma2 = par.sigma * par.sigma; - gRoadY = par.roadY; - gRoadZ = par.roadZ; - gminNumberOfClusters = par.minNumberOfClusters; - } - void setParameters(const std::vector& par) - { - gzWin = par[0]; - gminPt = par[1]; - gmaxDCAxy = par[3]; - gmaxDCAz = par[4]; - gSeedingLayer1 = par[5]; - gSeedingLayer2 = par[6]; - gSeedingLayer3 = par[7]; - gSigma2 = par[8] * par[8]; - gmaxChi2PerCluster = par[9]; - gmaxChi2PerTrack = par[10]; - gRoadY = par[11]; - gRoadZ = par[12]; - gminNumberOfClusters = par[13]; - } - void setParametersCosmics() - { - // seed "windows" in z and phi: makeSeeds - gzWin = 84.; // length of the L3 - gminPt = 10.; - // Maximal accepted impact parameters for the seeds - gmaxDCAxy = 19.4; // radius of the L3 - gmaxDCAz = 42.; // half-lenght of the L3 - // Space point resolution - gSigma2 = 0.2 * 0.2; - // Tracking "road" from layer to layer - gRoadY = 1.5; // Chip size in Y - gRoadZ = 3.0; // Chip size in Z - } - - void setVertices(const std::vector& vertices) - { - mVertices = &vertices; - } - - Double_t getX() const { return mX; } - Double_t getY() const { return mY; } - Double_t getZ() const { return mZ; } - Double_t getSigmaX() const { return mSigmaX; } - Double_t getSigmaY() const { return mSigmaY; } - Double_t getSigmaZ() const { return mSigmaZ; } - o2::MCCompLabel cookLabel(TrackITSExt& t, Float_t wrong) const; - void setExternalIndices(TrackITSExt& t) const; - Double_t getBz() const; - void setBz(Double_t bz) { mBz = bz; } - - void setNumberOfThreads(Int_t n) { mNumOfThreads = n; } - Int_t getNumberOfThreads() const { return mNumOfThreads; } - - using TrackInserter = std::function; - // These functions must be implemented - template - void process(gsl::span clusters, gsl::span::iterator& it, const o2::itsmft::TopologyDictionary* dict, U& tracks, V& clusIdx, o2::itsmft::ROFRecord& rof) - { - TrackInserter inserter = [&tracks, &clusIdx, this](const TrackITSExt& t) -> int { - // convert internal track to output format - auto& trackNew = tracks.emplace_back(t); - int noc = t.getNumberOfClusters(); - int clEntry = clusIdx.size(); - for (int i = 0; i < noc; i++) { - const Cluster* c = this->getCluster(t.getClusterIndex(i)); - Int_t idx = c - &mClusterCache[0]; // Index of this cluster in event - clusIdx.emplace_back(this->mFirstInFrame + idx); - } - trackNew.setClusterRefs(clEntry, noc); - trackNew.setPattern(0x7f); // this tracker finds only complete tracks - return tracks.size(); - }; - process(clusters, it, dict, inserter, rof); - } - void process(gsl::span const& clusters, gsl::span::iterator& it, const o2::itsmft::TopologyDictionary* dict, TrackInserter& inserter, o2::itsmft::ROFRecord& rof); - const Cluster* getCluster(Int_t index) const; - - void setGeometry(o2::its::GeometryTGeo* geom); - void setMCTruthContainers(const o2::dataformats::MCTruthContainer* clsLabels, std::vector* trkLabels) - { - mClsLabels = clsLabels; - mTrkLabels = trkLabels; - } - - void setContinuousMode(bool mode) { mContinuousMode = mode; } - bool getContinuousMode() { return mContinuousMode; } - - static void setMostProbablePt(float pt) { mMostProbablePt = pt; } - static auto getMostProbablePt() { return mMostProbablePt; } - - // internal helper classes - class ThreadData; - class Layer; - - protected: - static constexpr int kNLayers = 7; - int loadClusters(); - void unloadClusters(); - std::tuple processLoadedClusters(TrackInserter& inserter); - - std::vector trackInThread(Int_t first, Int_t last); - o2::its::TrackITSExt cookSeed(const Point3Df& r1, Point3Df& r2, const Point3Df& tr3, float rad2, float rad3, float_t alpha, float_t bz); - void makeSeeds(std::vector& seeds, Int_t first, Int_t last); - void trackSeeds(std::vector& seeds); - - Bool_t attachCluster(Int_t& volID, Int_t nl, Int_t ci, TrackITSExt& t, const TrackITSExt& o) const; - - void makeBackPropParam(std::vector& seeds) const; - bool makeBackPropParam(TrackITSExt& track) const; - - private: - /*** Tracking parameters ***/ - // seed "windows" in z and phi: makeSeeds - static Float_t gzWin; - static Float_t gminPt; - static Float_t mMostProbablePt; ///< settable most probable pt - // Maximal accepted impact parameters for the seeds - static Float_t gmaxDCAxy; - static Float_t gmaxDCAz; - // Layers for the seeding - static Int_t gSeedingLayer1; - static Int_t gSeedingLayer2; - static Int_t gSeedingLayer3; - // Space point resolution - static Float_t gSigma2; - // Max accepted chi2 - static Float_t gmaxChi2PerCluster; - static Float_t gmaxChi2PerTrack; - // Tracking "road" from layer to layer - static Float_t gRoadY; - static Float_t gRoadZ; - // Minimal number of attached clusters - static Int_t gminNumberOfClusters; - - bool mContinuousMode = true; ///< triggered or cont. mode - const o2::its::GeometryTGeo* mGeom = nullptr; /// interface to geometry - const o2::dataformats::MCTruthContainer* mClsLabels = nullptr; /// Cluster MC labels - std::vector* mTrkLabels = nullptr; /// Track MC labels - std::uint32_t mFirstInFrame = 0; ///< Index of the 1st cluster of a frame (within the loaded vector of clusters) - - Int_t mNumOfThreads; ///< Number of tracking threads - - Double_t mBz; ///< Effective Z-component of the magnetic field (kG) - - const std::vector* mVertices = nullptr; - Double_t mX = 0.; ///< X-coordinate of the primary vertex - Double_t mY = 0.; ///< Y-coordinate of the primary vertex - Double_t mZ = 0.; ///< Z-coordinate of the primary vertex - - Double_t mSigmaX = 2.; ///< error of the primary vertex position in X - Double_t mSigmaY = 2.; ///< error of the primary vertex position in Y - Double_t mSigmaZ = 2.; ///< error of the primary vertex position in Z - - static Layer sLayers[kNLayers]; ///< Layers filled with clusters - std::vector mSeeds; ///< Track seeds - - std::vector mClusterCache; - - ClassDefNV(CookedTracker, 1); -}; - -class CookedTracker::Layer -{ - public: - Layer(); - Layer(const Layer&) = delete; - Layer& operator=(const Layer& tr) = delete; - - void init(); - Bool_t insertCluster(const Cluster* c); - void setR(Double_t r) { mR = r; } - void unloadClusters(); - void selectClusters(std::vector& s, Float_t phi, Float_t dy, Float_t z, Float_t dz); - Int_t findClusterIndex(Float_t z) const; - Float_t getR() const { return mR; } - const Cluster* getCluster(Int_t i) const { return mClusters[i]; } - Float_t getAlphaRef(Int_t i) const { return mAlphaRef[i]; } - Float_t getClusterPhi(Int_t i) const { return mPhi[i]; } - Int_t getNumberOfClusters() const { return mClusters.size(); } - void setGeometry(o2::its::GeometryTGeo* geom) { mGeom = geom; } - - protected: - enum { kNSectors = 21 }; - - Float_t mR; ///< mean radius of this layer - const o2::its::GeometryTGeo* mGeom = nullptr; ///< interface to geometry - std::vector mClusters; ///< All clusters - std::vector mAlphaRef; ///< alpha of the reference plane - std::vector mPhi; ///< cluster phi - std::vector> mSectors[kNSectors]; ///< Cluster indices sector-by-sector -}; -} // namespace its -} // namespace o2 -#endif /* ALICEO2_ITS_COOKEDTRACKER_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/RecoGeomHelper.h b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/RecoGeomHelper.h index f9d3f1ae46752..a7d814f02d011 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/RecoGeomHelper.h +++ b/Detectors/ITSMFT/ITS/reconstruction/include/ITSReconstruction/RecoGeomHelper.h @@ -103,7 +103,7 @@ struct RecoGeomHelper { static constexpr float ladderWidth() { return o2::itsmft::SegmentationAlpide::SensorSizeRows; } static constexpr float ladderWidthInv() { return 1. / ladderWidth(); } - void init(); + void init(int minLayer = 0, int maxLayer = getNLayers()); void print() const; ClassDefNV(RecoGeomHelper, 0); 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/ClustererTask.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/ClustererTask.cxx deleted file mode 100644 index fb4e4ac7b6fa2..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/src/ClustererTask.cxx +++ /dev/null @@ -1,163 +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 ClustererTask.cxx -/// \brief Implementation of the ITS cluster finder task - -#include "DetectorsCommonDataFormats/DetID.h" -#include "ITSReconstruction/ClustererTask.h" -#include "MathUtils/Cartesian.h" -#include "MathUtils/Utils.h" -#include -#include -#include - -using namespace o2::its; - -//_____________________________________________________________________ -ClustererTask::ClustererTask(bool useMC, bool raw) : mRawDataMode(raw), - mUseMCTruth(useMC && (!raw)) -{ - LOG(info) << Class()->GetName() << ": MC digits mode: " << (mRawDataMode ? "OFF" : "ON") - << " | Use MCtruth: " << (mUseMCTruth ? "ON" : "OFF"); - - mClusterer.setNChips(o2::itsmft::ChipMappingITS::getNChips()); -} - -//_____________________________________________________________________ -ClustererTask::~ClustererTask() -{ - mCompClus.clear(); - mClsLabels.clear(); -} - -//_____________________________________________________________________ -void ClustererTask::Init() -{ - /// Inititializes the clusterer and connects input and output container - - if (mReader) { - return; // already initialized - } - - // create reader according to requested raw of MC mode - if (mRawDataMode) { - mReaderRaw = std::make_unique>(); - mReader = mReaderRaw.get(); - } else { // clusterizer of digits - mReaderMC = std::make_unique(); - mReader = mReaderMC.get(); - } - - mClusterer.print(); - - return; -} - -//_____________________________________________________________________ -void ClustererTask::run(const std::string inpName, const std::string outName) -{ - // standalone execution - Init(); // create reader, clusterer - - if (mRawDataMode) { - - mReaderRaw->openInput(inpName); - mClusterer.process(1, *mReaderRaw.get(), &mCompClus, &mPatterns, &mROFRecVec, nullptr); - - auto basename = outName.substr(0, outName.size() - sizeof("root")); - auto nFiles = int(mROFRecVec.size() / maxROframe); - int i = 0; - for (; i < nFiles; i++) { - writeTree(basename, i); - } - writeTree(basename, i); // The remainder - - } else { - - mReaderMC->openInput(inpName, o2::detectors::DetID("ITS")); - - TFile outFile(outName.data(), "new"); - if (!outFile.IsOpen()) { - LOG(fatal) << "Failed to open output file " << outName; - } - - TTree outTree("o2sim", "ITS Clusters"); - - auto compClusPtr = &mCompClus; - outTree.Branch("ITSClusterComp", &compClusPtr); - - auto rofRecVecPtr = &mROFRecVec; - outTree.Branch("ITSClustersROF", &rofRecVecPtr); - - auto clsLabelsPtr = &mClsLabels; - if (mUseMCTruth && mReaderMC->getDigitsMCTruth()) { - // digit labels are provided directly to clusterer - outTree.Branch("ITSClusterMCTruth", &clsLabelsPtr); - } else { - mUseMCTruth = false; - } - LOG(info) << Class()->GetName() << " | MCTruth: " << (mUseMCTruth ? "ON" : "OFF"); - - outTree.Branch("ITSClusterPatt", &mPatterns); - - std::vector mc2rof, *mc2rofPtr = &mc2rof; - if (mUseMCTruth) { - auto mc2rofOrig = mReaderMC->getMC2ROFRecords(); - mc2rof.reserve(mc2rofOrig.size()); - for (const auto& m2r : mc2rofOrig) { // clone from the span - mc2rof.push_back(m2r); - } - outTree.Branch("ITSClustersMC2ROF", mc2rofPtr); - } - - // loop over entries of the input tree - while (mReaderMC->readNextEntry()) { - mClusterer.process(1, *mReaderMC.get(), &mCompClus, &mPatterns, &mROFRecVec, &mClsLabels); - } - - outTree.Fill(); - outTree.Write(); - } - - mClusterer.clear(); -} - -void ClustererTask::writeTree(std::string basename, int i) -{ - auto name = basename + std::to_string(i) + ".root"; - TFile outFile(name.data(), "new"); - if (!outFile.IsOpen()) { - LOG(fatal) << "Failed to open output file " << name; - } - TTree outTree("o2sim", "ITS Clusters"); - - size_t max = (i + 1) * maxROframe; - auto lastf = (max < mROFRecVec.size()) ? mROFRecVec.begin() + max : mROFRecVec.end(); - std::vector rofRecBuffer(mROFRecVec.begin() + i * maxROframe, lastf); - std::vector* rofRecPtr = &rofRecBuffer; - outTree.Branch("ITSClustersROF", rofRecPtr); - - auto first = rofRecBuffer[0].getFirstEntry(); - auto last = rofRecBuffer.back().getFirstEntry() + rofRecBuffer.back().getNEntries(); - - std::vector compClusBuffer, *compClusPtr = &compClusBuffer; - compClusBuffer.assign(&mCompClus[first], &mCompClus[last]); - outTree.Branch("ITSClusterComp", &compClusPtr); - outTree.Branch("ITSClusterPatt", &mPatterns); - - for (auto& rof : rofRecBuffer) { - rof.setFirstEntry(rof.getFirstEntry() - first); - } - - outTree.Fill(); - outTree.Write(); -} diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/CookedConfigParam.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/CookedConfigParam.cxx deleted file mode 100644 index 81087744b04a9..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/src/CookedConfigParam.cxx +++ /dev/null @@ -1,22 +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 "ITSReconstruction/CookedConfigParam.h" - -namespace o2 -{ -namespace its -{ -static auto& sITSCookedTrackerParam = o2::its::CookedConfigParam::Instance(); - -O2ParamImpl(o2::its::CookedConfigParam); -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/reconstruction/src/CookedTracker.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/CookedTracker.cxx deleted file mode 100644 index 5c804f6705dfd..0000000000000 --- a/Detectors/ITSMFT/ITS/reconstruction/src/CookedTracker.cxx +++ /dev/null @@ -1,865 +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 CookedTracker.cxx -/// \brief Implementation of the "Cooked Matrix" ITS tracker -/// \author iouri.belikov@cern.ch - -//------------------------------------------------------------------------- -// A stand-alone ITS tracker -// The pattern recongintion based on the "cooked covariance" approach -//------------------------------------------------------------------------- -#include -#include -#include - -#include -#include - -#include - -#include "CommonConstants/MathConstants.h" -#include "DetectorsBase/Propagator.h" -#include "Field/MagneticField.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/TopologyDictionary.h" -#include "ITSReconstruction/CookedTracker.h" -#include "MathUtils/Utils.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" - -using namespace o2::its; -using namespace o2::itsmft; -using namespace o2::constants::math; -using o2::field::MagneticField; -using Label = o2::MCCompLabel; - -/*** Tracking parameters ***/ -// seed "windows" in z and phi: makeSeeds -Float_t CookedTracker::gzWin = 0.33; -Float_t CookedTracker::gminPt = 0.05; -Float_t CookedTracker::mMostProbablePt = o2::track::kMostProbablePt; -// Maximal accepted impact parameters for the seeds -Float_t CookedTracker::gmaxDCAxy = 3.; -Float_t CookedTracker::gmaxDCAz = 3.; -// Layers for the seeding -Int_t CookedTracker::gSeedingLayer1 = 6; -Int_t CookedTracker::gSeedingLayer2 = 4; -Int_t CookedTracker::gSeedingLayer3 = 5; -// Space point resolution -Float_t CookedTracker::gSigma2 = 0.0005 * 0.0005; -// Max accepted chi2 -Float_t CookedTracker::gmaxChi2PerCluster = 20.; -Float_t CookedTracker::gmaxChi2PerTrack = 30.; -// Tracking "road" from layer to layer -Float_t CookedTracker::gRoadY = 0.2; -Float_t CookedTracker::gRoadZ = 0.3; -// Minimal number of attached clusters -Int_t CookedTracker::gminNumberOfClusters = 4; - -const float kPI = 3.14159f; -const float k2PI = 2 * kPI; - -//************************************************ -// TODO: -//************************************************ -// Seeding: -// Precalculate cylidnrical (r,phi) for the clusters; -// use exact r's for the clusters - -CookedTracker::Layer CookedTracker::sLayers[CookedTracker::kNLayers]; - -CookedTracker::CookedTracker(Int_t n) : mNumOfThreads(n), mBz(0.) -{ - //-------------------------------------------------------------------- - // This default constructor needs to be provided - //-------------------------------------------------------------------- - const Double_t klRadius[7] = {2.34, 3.15, 3.93, 19.61, 24.55, 34.39, 39.34}; // tdr6 - - for (Int_t i = 0; i < kNLayers; i++) { - sLayers[i].setR(klRadius[i]); - } -} - -//__________________________________________________________________________ -Label CookedTracker::cookLabel(TrackITSExt& t, Float_t wrong) const -{ - //-------------------------------------------------------------------- - // This function "cooks" a track label. - // A label<0 indicates that some of the clusters are wrongly assigned. - //-------------------------------------------------------------------- - Int_t noc = t.getNumberOfClusters(); - std::map labelOccurence; - - for (int i = noc; i--;) { - const Cluster* c = getCluster(t.getClusterIndex(i)); - Int_t idx = c - &mClusterCache[0] + mFirstInFrame; // Index of this cluster in event - auto labels = mClsLabels->getLabels(idx); - - for (auto lab : labels) { // check all labels of the cluster - if (lab.isEmpty()) { - break; // all following labels will be empty also - } - // was this label already accounted for ? - labelOccurence[lab]++; - } - } - Label lab; - Int_t maxL = 0; // find most encountered label - for (auto [label, count] : labelOccurence) { - if (count <= maxL) { - continue; - } - maxL = count; - lab = label; - } - - if ((1. - Float_t(maxL) / noc) > wrong) { - // change the track ID to negative - lab.setFakeFlag(); - } - // t.SetFakeRatio((1.- Float_t(maxL)/noc)); - return lab; -} - -Double_t CookedTracker::getBz() const -{ - return mBz; -} - -static Double_t f1(Double_t x1, Double_t y1, Double_t x2, Double_t y2, Double_t x3, Double_t y3) -{ - //----------------------------------------------------------------- - // Initial approximation of the track curvature - //----------------------------------------------------------------- - Double_t d = (x2 - x1) * (y3 - y2) - (x3 - x2) * (y2 - y1); - Double_t a = - 0.5 * ((y3 - y2) * (y2 * y2 - y1 * y1 + x2 * x2 - x1 * x1) - (y2 - y1) * (y3 * y3 - y2 * y2 + x3 * x3 - x2 * x2)); - Double_t b = - 0.5 * ((x2 - x1) * (y3 * y3 - y2 * y2 + x3 * x3 - x2 * x2) - (x3 - x2) * (y2 * y2 - y1 * y1 + x2 * x2 - x1 * x1)); - - Double_t xr = TMath::Abs(d / (d * x1 - a)), yr = TMath::Abs(d / (d * y1 - b)); - - Double_t crv = xr * yr / sqrt(xr * xr + yr * yr); - if (d > 0) { - crv = -crv; - } - - return crv; -} - -static Double_t f2(Double_t x1, Double_t y1, Double_t x2, Double_t y2, Double_t x3, Double_t y3) -{ - //----------------------------------------------------------------- - // Initial approximation of the x-coordinate of the center of curvature - //----------------------------------------------------------------- - - Double_t k1 = (y2 - y1) / (x2 - x1), k2 = (y3 - y2) / (x3 - x2); - Double_t x0 = 0.5 * (k1 * k2 * (y1 - y3) + k2 * (x1 + x2) - k1 * (x2 + x3)) / (k2 - k1); - - return x0; -} - -static Double_t f3(Double_t x1, Double_t y1, Double_t x2, Double_t y2, Double_t z1, Double_t z2) -{ - //----------------------------------------------------------------- - // Initial approximation of the tangent of the track dip angle - //----------------------------------------------------------------- - return (z1 - z2) / sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); -} - -o2::its::TrackITSExt CookedTracker::cookSeed(const Point3Df& r1, Point3Df& r2, const Point3Df& tr3, float rad2, float rad3, float_t alpha, float_t bz) -// const Float_t r1[4], const Float_t r2[4], const Float_t tr3[4], Double_t alpha, Double_t bz) -{ - //-------------------------------------------------------------------- - // This is the main cooking function. - // Creates seed parameters out of provided clusters. - //-------------------------------------------------------------------- - - Double_t ca = TMath::Cos(alpha), sa = TMath::Sin(alpha); - Double_t x1 = r1.X() * ca + r1.Y() * sa, y1 = -r1.X() * sa + r1.Y() * ca, z1 = r1.Z(); - Double_t x2 = r2.X() * ca + r2.Y() * sa, y2 = -r2.X() * sa + r2.Y() * ca, z2 = r2.Z(); - Double_t x3 = tr3.X(), y3 = tr3.Y(), z3 = tr3.Z(); - - std::array par; - par[0] = y3; - par[1] = z3; - Double_t crv = f1(x1, y1, x2, y2, x3, y3); // curvature - Double_t x0 = f2(x1, y1, x2, y2, x3, y3); // x-coordinate of the center - Double_t tgl12 = f3(x1, y1, x2, y2, z1, z2); - Double_t tgl23 = f3(x2, y2, x3, y3, z2, z3); - - Double_t sf = crv * (x3 - x0); // FIXME: sf must never be >= kAlmost1 - par[2] = sf; - - par[3] = 0.5 * (tgl12 + tgl23); - par[4] = (TMath::Abs(bz) < Almost0) ? 1 / CookedTracker::getMostProbablePt() : crv / (bz * B2C); - - std::array cov; - /* - for (Int_t i=0; i<15; i++) cov[i]=0.; - cov[0] =gSigma2*10; - cov[2] =gSigma2*10; - cov[5] =0.007*0.007*10; //FIXME all these lines - cov[9] =0.007*0.007*10; - cov[14]=0.1*0.1*10; - */ - const Double_t dlt = 0.0005; - Double_t fy = 1. / (rad2 - rad3); - Double_t tz = fy; - const auto big = sqrt(o2::constants::math::VeryBig); - auto cy = big; - if (TMath::Abs(bz) >= Almost0) { - auto tmp = dlt * bz * B2C; - cy = (f1(x1, y1, x2, y2 + dlt, x3, y3) - crv) / tmp; - cy *= 20; // FIXME: MS contribution to the cov[14] - } - Double_t s2 = gSigma2; - - cov[0] = s2; - cov[1] = 0.; - cov[2] = s2; - cov[3] = s2 * fy; - cov[4] = 0.; - cov[5] = s2 * fy * fy; - cov[6] = 0.; - cov[7] = s2 * tz; - cov[8] = 0.; - cov[9] = s2 * tz * tz; - cov[10] = s2 * cy; - cov[11] = 0.; - cov[12] = s2 * fy * cy; - cov[13] = 0.; - cov[14] = s2 * cy * cy; - - return o2::its::TrackITSExt(x3, alpha, par, cov); -} - -void CookedTracker::makeSeeds(std::vector& seeds, Int_t first, Int_t last) -{ - //-------------------------------------------------------------------- - // This is the main pattern recongition function. - // Creates seeds out of two clusters and another point. - //-------------------------------------------------------------------- - const float zv = getZ(); - - Layer& layer1 = sLayers[gSeedingLayer1]; - Layer& layer2 = sLayers[gSeedingLayer2]; - Layer& layer3 = sLayers[gSeedingLayer3]; - - auto bz = getBz(); - const Double_t maxC = (TMath::Abs(bz) < Almost0) ? 0.03 : TMath::Abs(bz * B2C / gminPt); - const Double_t kpWinC = TMath::ASin(0.5 * maxC * layer1.getR()) - TMath::ASin(0.5 * maxC * layer2.getR()); - const Double_t kpWinD = 2 * (TMath::ASin(gmaxDCAxy / layer2.getR()) - TMath::ASin(gmaxDCAxy / layer1.getR())); - const Double_t kpWin = std::max(kpWinC, kpWinD); - - for (Int_t n1 = first; n1 < last; n1++) { - const Cluster* c1 = layer1.getCluster(n1); - // - //auto lab = (mClsLabels->getLabels(c1 - &mClusterCache[0] + mFirstInFrame))[0]; - // - auto xyz1 = c1->getXYZGloRot(*mGeom); - auto z1 = xyz1.Z(); - auto r1 = xyz1.rho(); - - auto phi1 = layer1.getClusterPhi(n1); - auto tgl = std::abs((z1 - zv) / r1); - - auto zr2 = zv + layer2.getR() / r1 * (z1 - zv); - auto phir2 = phi1; - auto dz2 = gzWin * (1 + 2 * tgl); - - std::vector selected2; - float dy2 = kpWin * layer2.getR(); - layer2.selectClusters(selected2, phir2, dy2, zr2, dz2); - for (auto n2 : selected2) { - const Cluster* c2 = layer2.getCluster(n2); - // - //if ((mClsLabels->getLabels(c2 - &mClusterCache[0] + mFirstInFrame))[0] != lab) continue; - // - auto xyz2 = c2->getXYZGloRot(*mGeom); - auto z2 = xyz2.Z(); - auto r2 = xyz2.rho(); - - auto dx = xyz2.X() - xyz1.X(), dy = xyz2.Y() - xyz1.Y(); - auto d = (dx * xyz1.Y() - dy * xyz1.X()) / TMath::Sqrt(dx * dx + dy * dy); - auto phir3 = phi1 + TMath::ASin(d / r1) - TMath::ASin(d / layer3.getR()); - - auto zr3 = z1 + (layer3.getR() - r1) / (r2 - r1) * (z2 - z1); - auto dz3 = 0.5f * dz2; - - std::vector selected3; - float dy3 = 0.1 * kpWin * layer3.getR(); //Fixme - layer3.selectClusters(selected3, phir3, dy3, zr3, dz3); - for (auto n3 : selected3) { - const Cluster* c3 = layer3.getCluster(n3); - // - //if ((mClsLabels->getLabels(c3 - &mClusterCache[0] + mFirstInFrame))[0] != lab) continue; - // - auto xyz3 = c3->getXYZGloRot(*mGeom); - auto z3 = xyz3.Z(); - auto r3 = xyz3.rho(); - - zr3 = z1 + (r3 - r1) / (r2 - r1) * (z2 - z1); - if (std::abs(z3 - zr3) > 0.2 * dz3) { - continue; - } - - const Point3Df& txyz2 = c2->getXYZ(); // tracking coordinates - - TrackITSExt seed = cookSeed(xyz1, xyz3, txyz2, layer2.getR(), layer3.getR(), layer2.getAlphaRef(n2), getBz()); - - float ip[2]; - seed.getImpactParams(getX(), getY(), getZ(), getBz(), ip); - if (TMath::Abs(ip[0]) > gmaxDCAxy) { - continue; - } - if (TMath::Abs(ip[1]) > gmaxDCAz) { - continue; - } - { - Double_t xx0 = 0.008; // Rough layer thickness - Double_t radl = 9.36; // Radiation length of Si [cm] - Double_t rho = 2.33; // Density of Si [g/cm^3] - if (!seed.correctForMaterial(xx0, xx0 * radl * rho, kTRUE)) { - continue; - } - } - seed.setClusterIndex(gSeedingLayer1, n1); - seed.setClusterIndex(gSeedingLayer3, n3); - seed.setClusterIndex(gSeedingLayer2, n2); - seeds.push_back(seed); - } - } - } - /* - for (Int_t n1 = 0; n1 < nClusters1; n1++) { - Cluster* c1 = layer1.getCluster(n1); - ((Cluster*)c1)->goToFrameTrk(); - } - for (Int_t n2 = 0; n2 < nClusters2; n2++) { - Cluster* c2 = layer2.getCluster(n2); - ((Cluster*)c2)->goToFrameTrk(); - } - for (Int_t n3 = 0; n3 < nClusters3; n3++) { - Cluster* c3 = layer3.getCluster(n3); - ((Cluster*)c3)->goToFrameTrk(); - } - */ -} - -void CookedTracker::trackSeeds(std::vector& seeds) -{ - //-------------------------------------------------------------------- - // Loop over a subset of track seeds - //-------------------------------------------------------------------- - std::vector used[gSeedingLayer2]; - std::vector selec[gSeedingLayer2]; - for (Int_t l = gSeedingLayer2 - 1; l >= 0; l--) { - Int_t n = sLayers[l].getNumberOfClusters(); - used[l].resize(n, false); - selec[l].reserve(n / 100); - } - - for (auto& track : seeds) { - auto x = track.getX(); - auto y = track.getY(); - Float_t phi = track.getAlpha() + TMath::ATan2(y, x); - o2::math_utils::bringTo02Pi(phi); - float ip[2]; - track.getImpactParams(getX(), getY(), getZ(), getBz(), ip); - - auto z = track.getZ(); - auto crv = track.getCurvature(getBz()); - auto tgl = track.getTgl(); - Float_t r1 = sLayers[gSeedingLayer2].getR(); - - for (Int_t l = gSeedingLayer2 - 1; l >= 0; l--) { - Float_t r2 = sLayers[l].getR(); - selec[l].clear(); - if (TMath::Abs(ip[0]) > r2) { - break; - } - if (TMath::Abs(crv) < gRoadY / (0.5 * r1 * 0.5 * r1)) { - phi += TMath::ASin(ip[0] / r2) - TMath::ASin(ip[0] / r1); - z += tgl * (TMath::Sqrt(r2 * r2 - ip[0] * ip[0]) - TMath::Sqrt(r1 * r1 - ip[0] * ip[0])); - } else { // Fixme - phi += 0.5 * crv * (r2 - r1); - z += tgl / (0.5 * crv) * (TMath::ASin(0.5 * crv * r2) - TMath::ASin(0.5 * crv * r1)); - } - sLayers[l].selectClusters(selec[l], phi, gRoadY, z, gRoadZ * (1 + 2 * std::abs(tgl))); - r1 = r2; - } - - TrackITSExt best(track); - - Int_t volID = -1; - for (auto& ci3 : selec[3]) { - TrackITSExt t3(track); - if (used[3][ci3]) { - continue; - } - if (!attachCluster(volID, 3, ci3, t3, track)) { - continue; - } - if (t3.isBetter(best, gmaxChi2PerTrack)) { - best = t3; - } - - for (auto& ci2 : selec[2]) { - TrackITSExt t2(t3); - if (used[2][ci2]) { - continue; - } - if (!attachCluster(volID, 2, ci2, t2, t3)) { - continue; - } - if (t2.isBetter(best, gmaxChi2PerTrack)) { - best = t2; - } - - for (auto& ci1 : selec[1]) { - TrackITSExt t1(t2); - if (used[1][ci1]) { - continue; - } - if (!attachCluster(volID, 1, ci1, t1, t2)) { - continue; - } - if (t1.isBetter(best, gmaxChi2PerTrack)) { - best = t1; - } - - for (auto& ci0 : selec[0]) { - TrackITSExt t0(t1); - if (used[0][ci0]) { - continue; - } - if (!attachCluster(volID, 0, ci0, t0, t1)) { - continue; - } - if (t0.isBetter(best, gmaxChi2PerTrack)) { - best = t0; - } - volID = -1; - } - } - } - } - - if (best.getNumberOfClusters() >= gminNumberOfClusters) { - Int_t noc = best.getNumberOfClusters(); - for (Int_t ic = 3; ic < noc; ic++) { - Int_t index = best.getClusterIndex(ic); - Int_t l = (index & 0xf0000000) >> 28, c = (index & 0x0fffffff); - used[l][c] = true; - } - } - track = best; - } -} - -std::vector CookedTracker::trackInThread(Int_t first, Int_t last) -{ - //-------------------------------------------------------------------- - // This function is passed to a tracking thread - //-------------------------------------------------------------------- - std::vector seeds; - seeds.reserve(last - first + 1); - - for (const auto& vtx : *mVertices) { - mX = vtx.getX(); - mY = vtx.getY(); - mZ = vtx.getZ(); - makeSeeds(seeds, first, last); - } - - std::sort(seeds.begin(), seeds.end()); - - trackSeeds(seeds); - - makeBackPropParam(seeds); - - return seeds; -} - -void CookedTracker::process(gsl::span const& clusters, - gsl::span::iterator& pattIt, - const o2::itsmft::TopologyDictionary* dict, - TrackInserter& inserter, - o2::itsmft::ROFRecord& rof) -{ - //-------------------------------------------------------------------- - // This is the main tracking function - //-------------------------------------------------------------------- - if (mVertices == nullptr || mVertices->empty()) { - LOG(info) << "Not a single primary vertex provided. Skipping...\n"; - return; - } - LOG(info) << "\n CookedTracker::process(), number of threads: " << mNumOfThreads; - - auto start = std::chrono::system_clock::now(); - - mFirstInFrame = rof.getFirstEntry(); - - mClusterCache.reserve(rof.getNEntries()); - auto clusters_in_frame = rof.getROFData(clusters); - for (const auto& comp : clusters_in_frame) { - - auto pattID = comp.getPatternID(); - o2::math_utils::Point3D locXYZ; - float sigmaY2 = gSigma2, sigmaZ2 = gSigma2; - if (pattID != itsmft::CompCluster::InvalidPatternID) { - sigmaY2 = gSigma2; //dict.getErr2X(pattID); - sigmaZ2 = gSigma2; //dict.getErr2Z(pattID); - if (!dict->isGroup(pattID)) { - locXYZ = dict->getClusterCoordinates(comp); - } else { - o2::itsmft::ClusterPattern patt(pattIt); - locXYZ = dict->getClusterCoordinates(comp, patt); - } - } else { - o2::itsmft::ClusterPattern patt(pattIt); - locXYZ = dict->getClusterCoordinates(comp, patt, false); - } - auto sensorID = comp.getSensorID(); - // Inverse transformation to the local --> tracking - auto trkXYZ = mGeom->getMatrixT2L(sensorID) ^ locXYZ; - - Cluster c; - c.setSensorID(sensorID); - c.setPos(trkXYZ); - c.setErrors(sigmaY2, sigmaZ2, 0.f); - mClusterCache.push_back(c); - } - - auto nClFrame = loadClusters(); - - auto end = std::chrono::system_clock::now(); - std::chrono::duration diff = end - start; - LOG(info) << "Loading clusters: " << nClFrame << " in a single frame : " << diff.count() << " s"; - - start = end; - - auto [first, number] = processLoadedClusters(inserter); - rof.setFirstEntry(first); - rof.setNEntries(number); - - unloadClusters(); - end = std::chrono::system_clock::now(); - diff = end - start; - LOG(info) << "Processing time/clusters for single frame : " << diff.count() << " / " << nClFrame << " s"; - - start = end; -} - -std::tuple CookedTracker::processLoadedClusters(TrackInserter& inserter) -{ - //-------------------------------------------------------------------- - // This is the main tracking function for single frame, it is assumed that only clusters - // which may contribute to this frame is loaded - //-------------------------------------------------------------------- - Int_t numOfClusters = sLayers[gSeedingLayer1].getNumberOfClusters(); - if (!numOfClusters) { - return {0, 0}; - } - - std::vector>> futures(mNumOfThreads); - std::vector> seedArray(mNumOfThreads); - - for (Int_t t = 0, first = 0; t < mNumOfThreads; t++) { - Int_t rem = t < (numOfClusters % mNumOfThreads) ? 1 : 0; - Int_t last = first + (numOfClusters / mNumOfThreads) + rem; - futures[t] = std::async(std::launch::async, &CookedTracker::trackInThread, this, first, last); - first = last; - } - Int_t nSeeds = 0, ngood = 0; - int nAllTracks = 0, nTracks = 0; - for (Int_t t = 0; t < mNumOfThreads; t++) { - seedArray[t] = futures[t].get(); - nSeeds += seedArray[t].size(); - for (auto& track : seedArray[t]) { - if (track.getNumberOfClusters() < gminNumberOfClusters) { - continue; - } - - o2::dataformats::VertexBase vtx; - track.propagateToDCA(vtx, getBz()); - - nAllTracks = inserter(track); - nTracks++; - if (mTrkLabels) { - Label label = cookLabel(track, 0.); // For comparison only - if (label.getTrackID() >= 0) { - ngood++; - } - // the inserter returns the size of the track vector, the index of the last - // inserted track is thus n - 1 - mTrkLabels->emplace_back(label); - } - } - } - - if (nSeeds) { - LOG(info) << "Found tracks: " << nTracks; - LOG(info) << "CookedTracker::processLoadedClusters(), good_tracks:/seeds: " << ngood << '/' << nSeeds << "-> " - << Float_t(ngood) / nSeeds << '\n'; - } - // returning index of the first track and the number of add tracks - // inserted in this call - return {nAllTracks - nTracks, nTracks}; -} - -//____________________________________________________________ -void CookedTracker::makeBackPropParam(std::vector& seeds) const -{ - // refit in backward direction - for (auto& track : seeds) { - if (track.getNumberOfClusters() < gminNumberOfClusters) { - continue; - } - makeBackPropParam(track); - } -} - -//____________________________________________________________ -bool CookedTracker::makeBackPropParam(TrackITSExt& track) const -{ - // refit in backward direction - auto backProp = track.getParamOut(); - backProp = track; - backProp.resetCovariance(); - auto propagator = o2::base::Propagator::Instance(); - - Int_t noc = track.getNumberOfClusters(); - for (int ic = noc; ic--;) { // cluster indices are stored in inward direction - Int_t index = track.getClusterIndex(ic); - const Cluster* c = getCluster(index); - float alpha = mGeom->getSensorRefAlpha(c->getSensorID()); - if (!backProp.rotate(alpha)) { - return false; - } - if (!propagator->PropagateToXBxByBz(backProp, c->getX())) { - return false; - } - if (!backProp.update(static_cast&>(*c))) { - return false; - } - } - track.getParamOut() = backProp; - return true; -} - -int CookedTracker::loadClusters() -{ - //-------------------------------------------------------------------- - // This function reads the ITSU clusters from the tree, - // sort them, distribute over the internal tracker arrays, etc - //-------------------------------------------------------------------- - - if (mClusterCache.empty()) { - return 0; - } - - for (const auto& c : mClusterCache) { - Int_t layer = mGeom->getLayer(c.getSensorID()); - sLayers[layer].insertCluster(&c); - } - - std::vector> fut; - for (Int_t l = 0; l < kNLayers; l += mNumOfThreads) { - for (Int_t t = 0; t < mNumOfThreads; t++) { - if (l + t >= kNLayers) { - break; - } - auto f = std::async(std::launch::async, &CookedTracker::Layer::init, sLayers + (l + t)); - fut.push_back(std::move(f)); - } - for (size_t t = 0; t < fut.size(); t++) { - fut[t].wait(); - } - } - - return mClusterCache.size(); -} - -void CookedTracker::unloadClusters() -{ - //-------------------------------------------------------------------- - // This function unloads ITSU clusters from the RAM - //-------------------------------------------------------------------- - mClusterCache.clear(); - for (Int_t i = 0; i < kNLayers; i++) { - sLayers[i].unloadClusters(); - } -} - -const Cluster* CookedTracker::getCluster(Int_t index) const -{ - //-------------------------------------------------------------------- - // Return pointer to a given cluster - //-------------------------------------------------------------------- - Int_t l = (index & 0xf0000000) >> 28; - Int_t c = (index & 0x0fffffff) >> 00; - return sLayers[l].getCluster(c); -} - -CookedTracker::Layer::Layer() : mR(0) -{ - //-------------------------------------------------------------------- - // This default constructor needs to be provided - //-------------------------------------------------------------------- -} - -void CookedTracker::Layer::init() -{ - //-------------------------------------------------------------------- - // Sort clusters and cache their reference plane info in a thread - //-------------------------------------------------------------------- - std::sort(std::begin(mClusters), std::end(mClusters), - [](const Cluster* c1, const Cluster* c2) { return (c1->getZ() < c2->getZ()); }); - - Double_t r = 0.; - Int_t m = mClusters.size(); - for (Int_t i = 0; i < m; i++) { - const Cluster* c = mClusters[i]; - // Float_t xRef, aRef; - // mGeom->getSensorXAlphaRefPlane(c->getSensorID(),xRef, aRef); - mAlphaRef.push_back(mGeom->getSensorRefAlpha(c->getSensorID())); - auto xyz = c->getXYZGloRot(*mGeom); - r += xyz.rho(); - Float_t phi = xyz.Phi(); - o2::math_utils::bringTo02Pi(phi); - mPhi.push_back(phi); - Int_t s = phi * (int)kNSectors / k2PI; - mSectors[s < kNSectors ? s : kNSectors - 1].emplace_back(i, c->getZ()); - } - - if (m) { - mR = r / m; - } -} - -void CookedTracker::Layer::unloadClusters() -{ - //-------------------------------------------------------------------- - // Unload clusters from this layer - //-------------------------------------------------------------------- - mClusters.clear(); - mAlphaRef.clear(); - mPhi.clear(); - for (Int_t s = 0; s < kNSectors; s++) { - mSectors[s].clear(); - } -} - -Bool_t CookedTracker::Layer::insertCluster(const Cluster* c) -{ - //-------------------------------------------------------------------- - // This function inserts a cluster to this layer - //-------------------------------------------------------------------- - mClusters.push_back(c); - return kTRUE; -} - -Int_t CookedTracker::Layer::findClusterIndex(Float_t z) const -{ - //-------------------------------------------------------------------- - // This function returns the index of the first cluster with its fZ >= "z". - //-------------------------------------------------------------------- - auto found = std::upper_bound(std::begin(mClusters), std::end(mClusters), z, - [](Float_t zc, const Cluster* c) { return (zc < c->getZ()); }); - return found - std::begin(mClusters); -} - -void CookedTracker::Layer::selectClusters(std::vector& selec, Float_t phi, Float_t dy, Float_t z, Float_t dz) -{ - //-------------------------------------------------------------------- - // This function selects clusters within the "road" - //-------------------------------------------------------------------- - Float_t zMin = z - dz; - Float_t zMax = z + dz; - - o2::math_utils::bringTo02Pi(phi); - - Float_t dphi = dy / mR; - - int smin = (phi - dphi) / k2PI * (int)kNSectors; - int ds = (phi + dphi) / k2PI * (int)kNSectors - smin + 1; - - smin = (smin + kNSectors) % kNSectors; - - for (int is = 0; is < ds; is++) { - Int_t s = (smin + is) % kNSectors; - - auto cmp = [](Float_t zc, std::pair p) { return (zc < p.second); }; - auto imin = std::upper_bound(std::begin(mSectors[s]), std::end(mSectors[s]), zMin, cmp); - auto imax = std::upper_bound(imin, std::end(mSectors[s]), zMax, cmp); - for (; imin != imax; imin++) { - auto [i, zz] = *imin; - auto cdphi = std::abs(mPhi[i] - phi); - if (cdphi > dphi) { - if (cdphi > kPI) { - cdphi = k2PI - cdphi; - } - if (cdphi > dphi) { - continue; // check in Phi - } - } - selec.push_back(i); - } - } -} - -Bool_t CookedTracker::attachCluster(Int_t& volID, Int_t nl, Int_t ci, TrackITSExt& t, const TrackITSExt& o) const -{ - //-------------------------------------------------------------------- - // Try to attach a clusters with index ci to running track hypothesis - //-------------------------------------------------------------------- - Layer& layer = sLayers[nl]; - const Cluster* c = layer.getCluster(ci); - - Int_t vid = c->getSensorID(); - - if (vid != volID) { - volID = vid; - t = o; - Double_t alpha = layer.getAlphaRef(ci); - if (!t.propagate(alpha, c->getX(), getBz())) { - return kFALSE; - } - } - - Double_t chi2 = t.getPredictedChi2(*c); - if (chi2 > gmaxChi2PerCluster) { - return kFALSE; - } - - if (!t.update(*c, chi2)) { - return kFALSE; - } - t.setClusterIndex(nl, ci); - - Double_t xx0 = (nl > 2) ? 0.008 : 0.003; // Rough layer thickness - Double_t x0 = 9.36; // Radiation length of Si [cm] - Double_t rho = 2.33; // Density of Si [g/cm^3] - t.correctForMaterial(xx0, xx0 * x0 * rho, kTRUE); - return kTRUE; -} - -void CookedTracker::setGeometry(o2::its::GeometryTGeo* geom) -{ - /// attach geometry interface - mGeom = geom; - for (Int_t i = 0; i < kNLayers; i++) { - sLayers[i].setGeometry(geom); - } -} 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 c93cf03d0ed3d..3bc8ee0f5403b 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h +++ b/Detectors/ITSMFT/ITS/reconstruction/src/ITSReconstructionLinkDef.h @@ -15,13 +15,6 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::its::ClustererTask + ; -#pragma link C++ class o2::its::CookedTracker + ; - #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/RecoGeomHelper.cxx b/Detectors/ITSMFT/ITS/reconstruction/src/RecoGeomHelper.cxx index 8f2efef0b34cd..712ec6a022d16 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/src/RecoGeomHelper.cxx +++ b/Detectors/ITSMFT/ITS/reconstruction/src/RecoGeomHelper.cxx @@ -229,9 +229,9 @@ void RecoGeomHelper::RecoLayer::print() const } //_____________________________________________________________________ -void RecoGeomHelper::init() +void RecoGeomHelper::init(int minLayer, int maxLayer) { - for (int il = int(layers.size()); il--;) { + for (int il = maxLayer; --il >= minLayer;) { auto& lr = layers[il]; lr.id = il; lr.init(); 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/simulation/include/ITSSimulation/V3Layer.h b/Detectors/ITSMFT/ITS/simulation/include/ITSSimulation/V3Layer.h index 15683feac4613..bc4fd6b0dadd4 100644 --- a/Detectors/ITSMFT/ITS/simulation/include/ITSSimulation/V3Layer.h +++ b/Detectors/ITSMFT/ITS/simulation/include/ITSSimulation/V3Layer.h @@ -353,43 +353,49 @@ class V3Layer : public V11Geometry static const Int_t sIBNChipRows; ///< IB chip rows in module static const Double_t sIBChipZGap; ///< Gap between IB chips on Z - static const Double_t sIBModuleZLength; ///< IB Module Length along Z - static const Double_t sIBFPCWiderXPlus; ///< FPC protrusion at X>0 - static const Double_t sIBFPCWiderXNeg; ///< FPC protrusion at X<0 - static const Double_t sIBFlexCableAlThick; ///< Thickness of FPC Aluminum - static const Double_t sIBFPCAlGNDWidth; ///< Width of total FPC Al Gnd - static const Double_t sIBFPCAlAnodeWidth1; ///< Width of FPC Al Anode - static const Double_t sIBFPCAlAnodeWidth2; ///< Width of FPC Al Anode - static const Double_t sIBFlexCableKapThick; ///< Thickness of FPC Kapton - static const Double_t sIBFlexCablePolyThick; ///< Thickness of FPC Coverlay - static const Double_t sIBFlexCapacitorXWid; ///< IB capaictor X width - static const Double_t sIBFlexCapacitorYHi; ///< IB capaictor Y height - static const Double_t sIBFlexCapacitorZLen; ///< IB capaictor Z length - static const Double_t sIBColdPlateWidth; ///< IB cold plate X width - static const Double_t sIBColdPlateZLen; ///< IB cold plate Z length - static const Double_t sIBGlueThick; ///< IB glue thickness - static const Double_t sIBCarbonFleeceThick; ///< IB carbon fleece thickness - static const Double_t sIBCarbonPaperThick; ///< IB Carbon Paper Thickness - static const Double_t sIBCarbonPaperWidth; ///< IB Carbon Paper X Width - static const Double_t sIBCarbonPaperZLen; ///< IB Carbon Paper Z Length - static const Double_t sIBK13D2UThick; ///< IB k13d2u prepreg thickness - static const Double_t sIBCoolPipeInnerD; ///< IB cooling inner diameter - static const Double_t sIBCoolPipeThick; ///< IB cooling pipe thickness - static const Double_t sIBCoolPipeXDist; ///< IB cooling pipe separation - static const Double_t sIBCoolPipeZLen; ///< IB cooling pipe length - static const Double_t sIBTopVertexWidth1; ///< IB TopVertex width - static const Double_t sIBTopVertexWidth2; ///< IB TopVertex width - static const Double_t sIBTopVertexHeight; ///< IB TopVertex height - static const Double_t sIBTopVertexAngle; ///< IB TopVertex aperture angle - static const Double_t sIBSideVertexWidth; ///< IB SideVertex width - static const Double_t sIBSideVertexHeight; ///< IB SideVertex height - static const Double_t sIBTopFilamentSide; ///< IB TopFilament side - static const Double_t sIBTopFilamentAlpha; ///< IB TopFilament angle - static const Double_t sIBTopFilamentInterZ; ///< IB TopFilament Z interdist - static const Double_t sIBEndSupportThick; ///< IB end support thickness - static const Double_t sIBEndSupportZLen; ///< IB end support length - static const Double_t sIBEndSupportXUp; ///< IB end support X up wide - static const Double_t sIBEndSupportOpenPhi; ///< IB end support opening phi + static const Double_t sIBModuleZLength; ///< IB Module Length along Z + static const Double_t sIBFPCWiderXPlus; ///< FPC protrusion at X>0 + static const Double_t sIBFPCWiderXNeg; ///< FPC protrusion at X<0 + static const Double_t sIBFlexCableAlThick; ///< Thickness of FPC Aluminum + static const Double_t sIBFPCAlGNDWidth; ///< Width of total FPC Al Gnd + static const Double_t sIBFPCAlAnodeWidth1; ///< Width of FPC Al Anode + static const Double_t sIBFPCAlAnodeWidth2; ///< Width of FPC Al Anode + static const Double_t sIBFlexCableKapThick; ///< Thickness of FPC Kapton + static const Double_t sIBFlexCablePolyThick; ///< Thickness of FPC Coverlay + static const Double_t sIBFlexCapacitor1XWid; ///< IB small capacitor X width + static const Double_t sIBFlexCapacitor1YHi; ///< IB small capacitor Y height + static const Double_t sIBFlexCapacitor1ZLen; ///< IB small capacitor Z length + static const Double_t sIBFlexCapacitor22XWid; ///< IB large capacitor X width + static const Double_t sIBFlexCapacitor22YHi; ///< IB large capacitor Y height + static const Double_t sIBFlexCapacitor22ZLen; ///< IB large capacitor Z length + static const Double_t sIBFlexResistorXWid; ///< IB FPC resistor X width + static const Double_t sIBFlexResistorYHi; ///< IB FPC resistor Y height + static const Double_t sIBFlexResistorZLen; ///< IB FPC resistor Z length + static const Double_t sIBColdPlateWidth; ///< IB cold plate X width + static const Double_t sIBColdPlateZLen; ///< IB cold plate Z length + static const Double_t sIBGlueThick; ///< IB glue thickness + static const Double_t sIBCarbonFleeceThick; ///< IB carbon fleece thickness + static const Double_t sIBCarbonPaperThick; ///< IB Carbon Paper Thickness + static const Double_t sIBCarbonPaperWidth; ///< IB Carbon Paper X Width + static const Double_t sIBCarbonPaperZLen; ///< IB Carbon Paper Z Length + static const Double_t sIBK13D2UThick; ///< IB k13d2u prepreg thickness + static const Double_t sIBCoolPipeInnerD; ///< IB cooling inner diameter + static const Double_t sIBCoolPipeThick; ///< IB cooling pipe thickness + static const Double_t sIBCoolPipeXDist; ///< IB cooling pipe separation + static const Double_t sIBCoolPipeZLen; ///< IB cooling pipe length + static const Double_t sIBTopVertexWidth1; ///< IB TopVertex width + static const Double_t sIBTopVertexWidth2; ///< IB TopVertex width + static const Double_t sIBTopVertexHeight; ///< IB TopVertex height + static const Double_t sIBTopVertexAngle; ///< IB TopVertex aperture angle + static const Double_t sIBSideVertexWidth; ///< IB SideVertex width + static const Double_t sIBSideVertexHeight; ///< IB SideVertex height + static const Double_t sIBTopFilamentSide; ///< IB TopFilament side + static const Double_t sIBTopFilamentAlpha; ///< IB TopFilament angle + static const Double_t sIBTopFilamentInterZ; ///< IB TopFilament Z interdist + static const Double_t sIBEndSupportThick; ///< IB end support thickness + static const Double_t sIBEndSupportZLen; ///< IB end support length + static const Double_t sIBEndSupportXUp; ///< IB end support X up wide + static const Double_t sIBEndSupportOpenPhi; ///< IB end support opening phi static const Double_t sIBConnectorXWidth; ///< IB Connectors Width static const Double_t sIBConnectorYTot; ///< IB Connectors total height diff --git a/Detectors/ITSMFT/ITS/simulation/src/Detector.cxx b/Detectors/ITSMFT/ITS/simulation/src/Detector.cxx index 2304a9102092a..63d7a8ad8dfa2 100644 --- a/Detectors/ITSMFT/ITS/simulation/src/Detector.cxx +++ b/Detectors/ITSMFT/ITS/simulation/src/Detector.cxx @@ -61,6 +61,7 @@ using Segmentation = o2::itsmft::SegmentationAlpide; using namespace o2::its; #ifdef ENABLE_UPGRADES +#include "ITS3Simulation/DescriptorInnerBarrelITS3.h" using namespace o2::its3; #endif @@ -477,10 +478,11 @@ void Detector::createMaterials() Float_t dInox304 = 7.85; // Ceramic (for IB capacitors) (BaTiO3) + // Density includes soldering Float_t aCeramic[3] = {137.327, 47.867, 15.999}; Float_t zCeramic[3] = {56, 22, 8}; // Ba, Ti, O Float_t wCeramic[3] = {1, 1, 3}; // Molecular composition - Float_t dCeramic = 6.02; + Float_t dCeramic = 8.28; // Rohacell (C9 H13 N1 O2) Float_t aRohac[4] = {12.01, 1.01, 14.010, 16.}; @@ -1105,7 +1107,7 @@ void Detector::addAlignableVolumes() const TString detName = GetName(); TString path = Form("/cave_1/barrel_1/%s_2", GeometryTGeo::getITSVolPattern()); - TString sname = GeometryTGeo::composeSymNameITS((detName == "IT3")); + TString sname = GeometryTGeo::composeSymNameITS(); LOG(debug) << sname << " <-> " << path; @@ -1116,15 +1118,19 @@ void Detector::addAlignableVolumes() const Int_t lastUID = 0; for (Int_t lr = 0; lr < mNumberLayers; lr++) { if (lr < mNumberInnerLayers) { +#ifdef ENABLE_UPGRADES if (detName == "ITS") { ((DescriptorInnerBarrelITS2*)mDescriptorIB.get())->addAlignableVolumesLayer(lr, mWrapperLayerId[lr], path, lastUID); + } else { + ((DescriptorInnerBarrelITS3*)mDescriptorIB.get())->addAlignableVolumesLayer(lr, mWrapperLayerId[lr], path, lastUID); } +#else + ((DescriptorInnerBarrelITS2*)mDescriptorIB.get())->addAlignableVolumesLayer(lr, mWrapperLayerId[lr], path, lastUID); +#endif } else { addAlignableVolumesLayer(lr, path, lastUID); } } - - return; } void Detector::addAlignableVolumesLayer(int lr, TString& parent, Int_t& lastUID) const @@ -1147,8 +1153,6 @@ void Detector::addAlignableVolumesLayer(int lr, TString& parent, Int_t& lastUID) for (Int_t hb = start; hb < nhbarrel; hb++) { addAlignableVolumesHalfBarrel(lr, hb, path, lastUID); } - - return; } void Detector::addAlignableVolumesHalfBarrel(Int_t lr, Int_t hb, TString& parent, Int_t& lastUID) const @@ -1176,8 +1180,6 @@ void Detector::addAlignableVolumesHalfBarrel(Int_t lr, Int_t hb, TString& parent for (int st = 0; st < nstaves; st++) { addAlignableVolumesStave(lr, hb, st, path, lastUID); } - - return; } void Detector::addAlignableVolumesStave(Int_t lr, Int_t hb, Int_t st, TString& parent, Int_t& lastUID) const @@ -1204,8 +1206,6 @@ void Detector::addAlignableVolumesStave(Int_t lr, Int_t hb, Int_t st, TString& p for (Int_t sst = start; sst < nhstave; sst++) { addAlignableVolumesHalfStave(lr, hb, st, sst, path, lastUID); } - - return; } void Detector::addAlignableVolumesHalfStave(Int_t lr, Int_t hb, Int_t st, Int_t hst, TString& parent, Int_t& lastUID) const @@ -1235,8 +1235,6 @@ void Detector::addAlignableVolumesHalfStave(Int_t lr, Int_t hb, Int_t st, Int_t for (Int_t md = start; md < nmodules; md++) { addAlignableVolumesModule(lr, hb, st, hst, md, path, lastUID); } - - return; } void Detector::addAlignableVolumesModule(Int_t lr, Int_t hb, Int_t st, Int_t hst, Int_t md, TString& parent, Int_t& lastUID) const @@ -1265,8 +1263,6 @@ void Detector::addAlignableVolumesModule(Int_t lr, Int_t hb, Int_t st, Int_t hst for (Int_t ic = 0; ic < nchips; ic++) { addAlignableVolumesChip(lr, hb, st, hst, md, ic, path, lastUID); } - - return; } void Detector::addAlignableVolumesChip(Int_t lr, Int_t hb, Int_t st, Int_t hst, Int_t md, Int_t ch, TString& parent, diff --git a/Detectors/ITSMFT/ITS/simulation/src/V3Layer.cxx b/Detectors/ITSMFT/ITS/simulation/src/V3Layer.cxx index 33a1bedec74eb..e930aa23de030 100644 --- a/Detectors/ITSMFT/ITS/simulation/src/V3Layer.cxx +++ b/Detectors/ITSMFT/ITS/simulation/src/V3Layer.cxx @@ -61,9 +61,15 @@ const Double_t V3Layer::sIBFPCAlAnodeWidth1 = 13.0 * sMm; const Double_t V3Layer::sIBFPCAlAnodeWidth2 = 14.7 * sMm; const Double_t V3Layer::sIBFlexCableKapThick = 75.0 * sMicron; const Double_t V3Layer::sIBFlexCablePolyThick = 20.0 * sMicron; -const Double_t V3Layer::sIBFlexCapacitorXWid = 0.2 * sMm; -const Double_t V3Layer::sIBFlexCapacitorYHi = 0.2 * sMm; -const Double_t V3Layer::sIBFlexCapacitorZLen = 0.4 * sMm; +const Double_t V3Layer::sIBFlexCapacitor1XWid = 0.5 * sMm; +const Double_t V3Layer::sIBFlexCapacitor1YHi = 0.5 * sMm; +const Double_t V3Layer::sIBFlexCapacitor1ZLen = 1.0 * sMm; +const Double_t V3Layer::sIBFlexCapacitor22XWid = 0.7 * sMm; +const Double_t V3Layer::sIBFlexCapacitor22YHi = 0.6 * sMm; +const Double_t V3Layer::sIBFlexCapacitor22ZLen = 1.1 * sMm; +const Double_t V3Layer::sIBFlexResistorXWid = 0.2 * sMm; +const Double_t V3Layer::sIBFlexResistorYHi = 0.2 * sMm; +const Double_t V3Layer::sIBFlexResistorZLen = 0.4 * sMm; const Double_t V3Layer::sIBColdPlateWidth = 15.4 * sMm; const Double_t V3Layer::sIBColdPlateZLen = 290.0 * sMm; const Double_t V3Layer::sIBGlueThick = 50.0 * sMicron; @@ -599,8 +605,11 @@ TGeoVolume* V3Layer::createModuleInnerB(const Double_t xchip, const Double_t zch // the module as a TGeoVolume // // Updated: 03 Apr 2021 + // Updated: 03 Nov 2025 Change volume from BBox to Xtru to avoid fake overlaps Double_t xtot, ytot, ztot; + Double_t ymid, shrinkFactor = 0.73; + Double_t xv[5], yv[5]; Double_t xpos, ypos, zpos; const Int_t nameLen = 30; char volumeName[nameLen]; @@ -619,9 +628,25 @@ TGeoVolume* V3Layer::createModuleInnerB(const Double_t xchip, const Double_t zch Double_t ygnd = (static_cast(aluGndCableVol->GetShape()))->GetDY(); Double_t yano = (static_cast(aluAnodeCableVol->GetShape()))->GetDY(); - ytot = sIBGlueThick / 2 + ygnd + sIBFlexCableKapThick / 2 + yano + sIBFlexCapacitorYHi / 2; + ytot = sIBGlueThick / 2 + ygnd + sIBFlexCableKapThick / 2 + yano + sIBFlexCapacitor22YHi / 2; + ymid = sIBGlueThick / 2 + ygnd + sIBFlexCableKapThick / 2 + yano; - TGeoBBox* module = new TGeoBBox(xtot, ytot, ztot); + xv[0] = xtot; + yv[0] = -ytot; + xv[1] = xv[0]; + yv[1] = yv[0] + 6 * ymid; + xv[2] = xtot * shrinkFactor; + yv[2] = ytot; + xv[3] = -xtot; + yv[3] = yv[2]; + xv[4] = xv[3]; + yv[4] = yv[0]; + + TGeoXtru* module = new TGeoXtru(2); + module->DefinePolygon(6, xv, yv); + module->DefinePolygon(5, xv, yv); + module->DefineSection(0, -ztot); + module->DefineSection(1, ztot); // Now the volumes TGeoMedium* medAir = mgr->GetMedium(Form("%s_AIR$", GetDetName())); @@ -674,6 +699,7 @@ void V3Layer::createIBCapacitors(TGeoVolume* modvol, Double_t zchip, Double_t yz // // Created: 13 Feb 2018 Mario Sitta // Updated: 03 Apr 2019 Mario Sitta Fix positions (180' rotation) + // Updated: 31 Oct 2025 Mario Sitta Fix dimensions and weight // // Position of the various capacitors (A.Junique private communication @@ -705,63 +731,72 @@ void V3Layer::createIBCapacitors(TGeoVolume* modvol, Double_t zchip, Double_t yz Double_t xpos, ypos, zpos; Int_t nCapacitors; - TGeoVolume *capacitor, *resistor; + TGeoVolume *capacitorSmall, *capacitorLarge, *resistor; - // Check whether we already have the volume, otherwise create it - // (so as to avoid creating multiple copies of the very same volume + // Check whether we already have the volumes, otherwise create them + // (so as to avoid creating multiple copies of the very same volumes // for each layer) - capacitor = mgr->GetVolume("IBFPCCapacitor"); + // The "small" capacitor is the 1 uF substrate capacitor + // The "large" capacitor is the 22 uF analog/digital PS capacitor + capacitorSmall = mgr->GetVolume("IBFPCCapacitorSmall"); - if (!capacitor) { - TGeoBBox* capsh = new TGeoBBox(sIBFlexCapacitorXWid / 2, sIBFlexCapacitorYHi / 2, sIBFlexCapacitorZLen / 2); + if (!capacitorSmall) { + TGeoBBox* capSmsh = new TGeoBBox(sIBFlexCapacitor1XWid / 2, sIBFlexCapacitor1YHi / 2, sIBFlexCapacitor1ZLen / 2); + TGeoBBox* capLgsh = new TGeoBBox(sIBFlexCapacitor22XWid / 2, sIBFlexCapacitor22YHi / 2, sIBFlexCapacitor22ZLen / 2); TGeoMedium* medCeramic = mgr->GetMedium(Form("%s_CERAMIC$", GetDetName())); - capacitor = new TGeoVolume("IBFPCCapacitor", capsh, medCeramic); - capacitor->SetLineColor(kBlack); - capacitor->SetFillColor(kBlack); + capacitorSmall = new TGeoVolume("IBFPCCapacitorSmall", capSmsh, medCeramic); + capacitorSmall->SetLineColor(kBlack); + capacitorSmall->SetFillColor(kBlack); + + capacitorLarge = new TGeoVolume("IBFPCCapacitorLarge", capLgsh, medCeramic); + capacitorLarge->SetLineColor(kBlack); + capacitorLarge->SetFillColor(kBlack); - TGeoBBox* ressh = new TGeoBBox(sIBFlexCapacitorXWid / 2, // Resistors have - sIBFlexCapacitorYHi / 2, // the same dim's - sIBFlexCapacitorZLen / 2); // as capacitors + TGeoBBox* ressh = new TGeoBBox(sIBFlexResistorXWid / 2, + sIBFlexResistorYHi / 2, + sIBFlexResistorZLen / 2); resistor = new TGeoVolume("IBFPCResistor", ressh, medCeramic); resistor->SetLineColor(kBlack); resistor->SetFillColor(kBlack); } else { // Volumes already defined, get them + capacitorLarge = mgr->GetVolume("IBFPCCapacitorLarge"); resistor = mgr->GetVolume("IBFPCResistor"); } // Place all the capacitors (they are really a lot...) - ypos = yzero + sIBFlexCapacitorYHi / 2; + ypos = yzero + sIBFlexCapacitor22YHi / 2; xpos = xGroup1A; for (Int_t j = 0; j < sIBChipsPerRow; j++) { zpos = -mIBModuleZLength / 2 + j * (2 * zchip + sIBChipZGap) + zchip + zGroup1A[0]; - modvol->AddNode(capacitor, 2 * j + 1, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorLarge, 2 * j + 1, new TGeoTranslation(-xpos, ypos, -zpos)); zpos = -mIBModuleZLength / 2 + j * (2 * zchip + sIBChipZGap) + zchip + zGroup1A[1]; - modvol->AddNode(capacitor, 2 * j + 2, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorLarge, 2 * j + 2, new TGeoTranslation(-xpos, ypos, -zpos)); } nCapacitors = 2 * sIBChipsPerRow; xpos = xGroup1B; for (Int_t j = 0; j < sIBChipsPerRow; j++) { zpos = -mIBModuleZLength / 2 + j * (2 * zchip + sIBChipZGap) + zchip + zGroup1B; - modvol->AddNode(capacitor, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorLarge, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); } nCapacitors += sIBChipsPerRow; + ypos = yzero + sIBFlexCapacitor1YHi / 2; xpos = xGroup2; // We have only 8 in these group, missing the central one for (Int_t j = 0; j < sIBChipsPerRow - 1; j++) { zpos = -mIBModuleZLength / 2 + j * (2 * zchip + sIBChipZGap) + zchip + zGroup2; - modvol->AddNode(capacitor, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorSmall, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); } nCapacitors += (sIBChipsPerRow - 1); xpos = xGroup3; zpos = zGroup3; - modvol->AddNode(capacitor, 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorSmall, 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); nCapacitors++; for (Int_t j = 0; j < sIBChipsPerRow; j++) { @@ -771,10 +806,11 @@ void V3Layer::createIBCapacitors(TGeoVolume* modvol, Double_t zchip, Double_t yz xpos = xGroup4[0]; } zpos = -mIBModuleZLength / 2 + j * (2 * zchip + sIBChipZGap) + zchip + zGroup4[j]; - modvol->AddNode(capacitor, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorSmall, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); } nCapacitors += sIBChipsPerRow; + ypos = yzero + sIBFlexCapacitor22YHi / 2; for (Int_t j = 0; j < nGroup5A; j++) { if (j == 0) { xpos = xGroup5A[0]; @@ -782,14 +818,14 @@ void V3Layer::createIBCapacitors(TGeoVolume* modvol, Double_t zchip, Double_t yz xpos = xGroup5A[1]; } zpos = zGroup5A[j]; - modvol->AddNode(capacitor, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorLarge, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); } nCapacitors += nGroup5A; xpos = xGroup5B; for (Int_t j = 0; j < nGroup5B; j++) { zpos = zGroup5B[j]; - modvol->AddNode(capacitor, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); + modvol->AddNode(capacitorLarge, j + 1 + nCapacitors, new TGeoTranslation(-xpos, ypos, -zpos)); } // Place the resistors @@ -1061,7 +1097,7 @@ TGeoVolume* V3Layer::createStaveModelInnerB4(const TGeoManager* mgr) yv[1] = layerHeight + sIBSideVertexHeight + topfil->GetDZ(); ; xv[2] = sIBEndSupportXUp / 2; - yv[2] = sIBStaveHeight + sIBTopFilamentSide / sinD(-theta); // theta is neg + yv[2] = sIBStaveHeight + sIBTopFilamentSide / sinD(-theta) - 0.01; // theta is neg for (Int_t i = 0; i < 3; i++) { xv[3 + i] = -xv[2 - i]; yv[3 + i] = yv[2 - i]; diff --git a/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/CMakeLists.txt index 291ddffbf9475..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,9 +52,13 @@ 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) if(CUDA_ENABLED OR HIP_ENABLED) add_subdirectory(GPU) endif() + +add_subdirectory(test) 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 a1d52bff11f9d..5f56e3f272473 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/TimeFrameGPU.h @@ -24,63 +24,74 @@ namespace o2::its::gpu { -template -class TimeFrameGPU : 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(); - ~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); - void loadClustersDevice(const int, const int); - void createClustersDeviceArray(const int); - void loadClustersIndexTables(const int, const int); - void createClustersIndexTablesArray(const int iteration); - void createUsedClustersDevice(const int, const int); - void createUsedClustersDeviceArray(const int); + 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(bounded_vector&); - void downloadTrackITSExtDevice(bounded_vector&); - void downloadCellsNeighboursDevice(std::vector>>&, const int); + void createTrackITSExtDevice(const size_t); + void downloadTrackITSExtDevice(); + void downloadCellsNeighboursDevice(std::vector>&, const int); void downloadNeighboursLUTDevice(bounded_vector&, const int); void downloadCellsDevice(); void downloadCellsLUTDevice(); @@ -92,14 +103,19 @@ class TimeFrameGPU : 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 - int getNClustersInRofSpan(const int, const int, const int) const; + virtual bool isGPU() const noexcept final { return true; } + 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; } @@ -108,127 +124,124 @@ class TimeFrameGPU : 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; } const Cluster** getDeviceArrayUnsortedClusters() const { return mUnsortedClustersDeviceArray; } const int** getDeviceArrayClustersIndexTables() const { return mClustersIndexTablesDeviceArray; } std::vector getClusterSizes(); - const unsigned char** getDeviceArrayUsedClusters() const { return mUsedClustersDeviceArray; } + uint8_t** getDeviceArrayUsedClusters() const { return mUsedClustersDeviceArray; } const int** getDeviceROFrameClusters() const { return mROFramesClustersDeviceArray; } Tracklet** getDeviceArrayTracklets() { return mTrackletsDeviceArray; } 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; } 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); // Abstract owned and unowned memory allocations on specific stream - void allocMem(void**, size_t, bool); // Abstract owned and unowned memory allocations on default stream - TimeFrameGPUParameters mGpuParams; + 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 // 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; - const unsigned char** mUsedClustersDeviceArray; + 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 mCellSeedsDevice; + std::array mCellsDevice{}; + CellSeed** mCellsDeviceArray; + std::array mNeighboursIndexTablesDevice{}; + TrackSeedN* mTrackSeedsDevice{nullptr}; + int* mTrackSeedsLUTDevice{nullptr}; + unsigned int mNTracks{0}; + 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; // 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(), @@ -236,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 69d6799686654..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,39 +174,71 @@ 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); - -template -void trackSeedHandler(CellSeed* trackSeeds, - const TrackingFrameInfo** foundTrackingFrameInfo, - o2::its::TrackITSExt* tracks, - std::vector& minPtsHost, - const unsigned int nSeeds, - const float Bz, - const int startLevel, - float maxChi2ClusterAttachment, - float maxChi2NDF, - const o2::base::Propagator* propagator, - const o2::base::PropagatorF::MatCorrType matCorrType, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); + +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, + 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* 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); + } // 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 15fe6f05f7850..bcc20ace7bbc2 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h +++ b/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/Utils.h @@ -20,24 +20,39 @@ #include #include +#include "ITStracking/MathUtils.h" +#include "ITStracking/ExternalAllocator.h" + #include "GPUCommonDef.h" #include "GPUCommonHelpers.h" #include "GPUCommonLogger.h" +#include "GPUCommonDefAPI.h" +#ifdef GPUCA_GPUCODE +#include #ifndef __HIPCC__ #define THRUST_NAMESPACE thrust::cuda #else #define THRUST_NAMESPACE thrust::hip #endif +#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 namespace o2::its { +// FWD declarations +template +class IndexTableUtils; +class Tracklet; template using gpuPair = std::pair; @@ -282,6 +297,163 @@ class GPUTimer } }; #endif + +#ifdef GPUCA_GPUCODE +template +struct TypedAllocator { + using value_type = T; + using pointer = thrust::device_ptr; + using const_pointer = thrust::device_ptr; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + TypedAllocator() noexcept : mInternalAllocator(nullptr) {} + explicit TypedAllocator(ExternalAllocator* a) noexcept : mInternalAllocator(a) {} + + template + TypedAllocator(const TypedAllocator& o) noexcept : mInternalAllocator(o.mInternalAllocator) + { + } + + pointer allocate(size_type n) + { + void* raw = mInternalAllocator->allocateStack(n * sizeof(T)); + return thrust::device_pointer_cast(static_cast(raw)); + } + + void deallocate(pointer p, size_type n) noexcept + { + if (!p) { + return; + } + void* raw = thrust::raw_pointer_cast(p); + mInternalAllocator->deallocate(static_cast(raw), n * sizeof(T)); + } + + bool operator==(TypedAllocator const& o) const noexcept + { + return mInternalAllocator == o.mInternalAllocator; + } + bool operator!=(TypedAllocator const& o) const noexcept + { + return !(*this == o); + } + + private: + ExternalAllocator* mInternalAllocator; +}; + +GPUdii() gpuSpan getPrimaryVertices(const int rof, + const int* roframesPV, + const int nROF, + const uint8_t* mask, + const Vertex* vertices) +{ + const int start_pv_id = roframesPV[rof]; + const int stop_rof = rof >= nROF - 1 ? nROF : rof + 1; + size_t delta = mask[rof] ? roframesPV[stop_rof] - start_pv_id : 0; // return empty span if ROF is excluded + return gpuSpan(&vertices[start_pv_id], delta); +}; + +GPUdii() gpuSpan getPrimaryVertices(const int romin, + const int romax, + const int* roframesPV, + const int nROF, + const Vertex* vertices) +{ + const int start_pv_id = roframesPV[romin]; + const int stop_rof = romax >= nROF - 1 ? nROF : romax + 1; + return gpuSpan(&vertices[start_pv_id], roframesPV[stop_rof] - roframesPV[romin]); +}; + +GPUdii() gpuSpan getClustersOnLayer(const int rof, + const int totROFs, + const int layer, + const int** roframesClus, + const Cluster** clusters) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[layer][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[layer][stop_rof] - start_clus_id; + return gpuSpan(&(clusters[layer][start_clus_id]), delta); +} + +GPUdii() gpuSpan getTrackletsPerCluster(const int rof, + const int totROFs, + const int mode, + const int** roframesClus, + const Tracklet** tracklets) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[1][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[1][stop_rof] - start_clus_id; + return gpuSpan(&(tracklets[mode][start_clus_id]), delta); +} + +GPUdii() gpuSpan getNTrackletsPerCluster(const int rof, + const int totROFs, + const int mode, + const int** roframesClus, + int** ntracklets) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[1][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[1][stop_rof] - start_clus_id; + return gpuSpan(&(ntracklets[mode][start_clus_id]), delta); +} + +GPUdii() gpuSpan getNTrackletsPerCluster(const int rof, + const int totROFs, + const int mode, + const int** roframesClus, + const int** ntracklets) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[1][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[1][stop_rof] - start_clus_id; + return gpuSpan(&(ntracklets[mode][start_clus_id]), delta); +} + +GPUdii() gpuSpan getNLinesPerCluster(const int rof, + const int totROFs, + const int** roframesClus, + int* nlines) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[1][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[1][stop_rof] - start_clus_id; + return gpuSpan(&(nlines[start_clus_id]), delta); +} + +GPUdii() gpuSpan getNLinesPerCluster(const int rof, + const int totROFs, + const int** roframesClus, + const int* nlines) +{ + if (rof < 0 || rof >= totROFs) { + return gpuSpan(); + } + const int start_clus_id{roframesClus[1][rof]}; + const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; + const unsigned int delta = roframesClus[1][stop_rof] - start_clus_id; + return gpuSpan(&(nlines[start_clus_id]), delta); +} +#endif } // namespace gpu } // namespace o2::its 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 5b1d9194e1174..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexerTraitsGPU.h +++ /dev/null @@ -1,62 +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 -{ - -class VertexerTraitsGPU final : public VertexerTraits -{ - public: - void initialise(const TrackingParameters&, const int iteration = 0) final; - void adoptTimeFrame(TimeFrame<7>*) 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; - void computeVerticesHist(); - - bool isGPU() const noexcept final { return true; } - const char* getName() const noexcept final { return "GPU"; } - - protected: - IndexTableUtils* mDeviceIndexTableUtils; - gpu::TimeFrameGPU<7>* mTimeFrameGPU; - TimeFrameGPUParameters mTfGPUParams; -}; - -inline void VertexerTraitsGPU::adoptTimeFrame(TimeFrame<7>* tf) noexcept -{ - mTimeFrameGPU = static_cast*>(tf); - mTimeFrame = static_cast*>(tf); -} - -} // 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 059b1cdc29082..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/ITStrackingGPU/VertexingKernels.h +++ /dev/null @@ -1,57 +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 "ITStracking/MathUtils.h" -#include "ITStracking/Configuration.h" -#include "ITStracking/ClusterLines.h" -#include "ITStracking/Tracklet.h" - -#include "ITStrackingGPU/Utils.h" -#include "ITStrackingGPU/ClusterLinesGPU.h" -#include "ITStrackingGPU/VertexerTraitsGPU.h" -#include "ITStrackingGPU/TracerGPU.h" - -namespace o2::its::gpu -{ -#ifdef GPUCA_GPUCODE // GPUg() global kernels must only when compiled by GPU compiler -template -GPUg() void trackleterKernelMultipleRof( - const Cluster* clustersNextLayer, // 0 2 - const Cluster* clustersCurrentLayer, // 1 1 - const int* sizeNextLClusters, - const int* sizeCurrentLClusters, - const int* nextIndexTables, - Tracklet* Tracklets, - int* foundTracklets, - const IndexTableUtils* utils, - const unsigned int startRofId, - const unsigned int rofSize, - const float phiCut, - const size_t maxTrackletsPerCluster); -#endif -template -void trackletFinderHandler(const Cluster* clustersNextLayer, // 0 2 - const Cluster* clustersCurrentLayer, // 1 1 - const int* sizeNextLClusters, - const int* sizeCurrentLClusters, - const int* nextIndexTables, - Tracklet* Tracklets, - int* foundTracklets, - const IndexTableUtils* utils, - const unsigned int startRofId, - const unsigned int rofSize, - const float phiCut, - const size_t maxTrackletsPerCluster = 1e2); -} // namespace o2::its::gpu -#endif diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/CMakeLists.txt index 3dff67dbccd80..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_cuda_arch(${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 965bf27fdd12b..5fff30f5162b1 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TimeFrameGPU.cu @@ -24,492 +24,611 @@ #include "GPUCommonMath.h" #include "GPUCommonLogger.h" #include "GPUCommonHelpers.h" +#include "utils/strtag.h" namespace o2::its::gpu { -template -TimeFrameGPU::TimeFrameGPU() -{ - this->mIsGPU = true; -} - -template -void TimeFrameGPU::allocMemAsync(void** ptr, size_t size, Stream& stream, bool extAllocator) +template +void TimeFrameGPU::allocMemAsync(void** ptr, size_t size, Stream& stream, bool extAllocator, int32_t type) { if (extAllocator) { - *ptr = this->mAllocator->allocate(size); + *ptr = (this->mExternalAllocator)->allocate(size, type); } else { GPULog("Calling default CUDA allocator"); GPUChkErrS(cudaMallocAsync(reinterpret_cast(ptr), size, stream.get())); } } -template -void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator) +template +void TimeFrameGPU::allocMem(void** ptr, size_t size, bool extAllocator, int32_t type) { if (extAllocator) { - *ptr = this->mAllocator->allocate(size); + *ptr = (this->mExternalAllocator)->allocate(size, type); } else { GPULog("Calling default CUDA allocator"); GPUChkErrS(cudaMalloc(reinterpret_cast(ptr), size)); } } -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->getExtAllocator()); + allocMem(reinterpret_cast(&mIndexTableUtilsDevice), sizeof(IndexTableUtilsN), this->hasFrameworkAllocator()); } GPULog("gpu-transfer: loading IndexTableUtils object, for {:.2f} MB.", sizeof(IndexTableUtilsN) / constants::MB); GPUChkErrS(cudaMemcpy(mIndexTableUtilsDevice, &(this->mIndexTableUtils), sizeof(IndexTableUtilsN), cudaMemcpyHostToDevice)); } -template -void TimeFrameGPU::createUnsortedClustersDeviceArray(const int iteration) +template +void TimeFrameGPU::createUnsortedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating unsorted clusters array"); - allocMem(reinterpret_cast(&mUnsortedClustersDeviceArray), nLayers * sizeof(Cluster*), this->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mUnsortedClustersDevice.data(), nLayers * sizeof(Cluster*), cudaHostRegisterPortable)); - mPinnedUnsortedClusters.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mUnsortedClusters[iLayer].data(), this->mUnsortedClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); - mPinnedUnsortedClusters.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mUnsortedClusters[iLayer].data(), this->mUnsortedClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); + mPinnedUnsortedClusters.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mUnsortedClustersDevice[layer]), this->mUnsortedClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(mUnsortedClustersDevice[layer], this->mUnsortedClusters[layer].data(), this->mUnsortedClusters[layer].size() * sizeof(Cluster), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mUnsortedClustersDeviceArray[layer], &mUnsortedClustersDevice[layer], sizeof(Cluster*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -template -void TimeFrameGPU::createClustersDeviceArray(const int iteration) +template +void TimeFrameGPU::createClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating sorted clusters array"); - allocMem(reinterpret_cast(&mClustersDeviceArray), nLayers * sizeof(Cluster*), this->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mClustersDevice.data(), nLayers * sizeof(Cluster*), cudaHostRegisterPortable)); - mPinnedClusters.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mClusters[iLayer].data(), this->mClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); - mPinnedClusters.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mClusters[iLayer].data(), this->mClusters[iLayer].size() * sizeof(Cluster), cudaHostRegisterPortable)); + mPinnedClusters.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mClustersDevice[layer]), this->mClusters[layer].size() * sizeof(Cluster), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(mClustersDevice[layer], this->mClusters[layer].data(), this->mClusters[layer].size() * sizeof(Cluster), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mClustersDeviceArray[layer], &mClustersDevice[layer], sizeof(Cluster*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -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->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mClustersIndexTablesDevice.data(), nLayers * sizeof(int*), cudaHostRegisterPortable)); - mPinnedClustersIndexTables.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mIndexTables[iLayer].data(), this->mIndexTables[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); - mPinnedClustersIndexTables.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mIndexTables[iLayer].data(), this->mIndexTables[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); + mPinnedClustersIndexTables.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mClustersIndexTablesDevice[layer]), this->mIndexTables[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(mClustersIndexTablesDevice[layer], this->mIndexTables[layer].data(), this->mIndexTables[layer].size() * sizeof(int), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mClustersIndexTablesDeviceArray[layer], &mClustersIndexTablesDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -template -void TimeFrameGPU::createUsedClustersDeviceArray(const int iteration) +template +void TimeFrameGPU::createUsedClustersDeviceArray(const int maxLayers) { - if (!iteration) { + { GPUTimer timer("creating used clusters flags"); - allocMem(reinterpret_cast(&mUsedClustersDeviceArray), nLayers * sizeof(unsigned char*), this->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mUsedClustersDevice.data(), nLayers * sizeof(unsigned char*), cudaHostRegisterPortable)); - mPinnedUsedClusters.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mUsedClusters[iLayer].data(), this->mUsedClusters[iLayer].size() * sizeof(unsigned char), cudaHostRegisterPortable)); - mPinnedUsedClusters.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mUsedClusters[iLayer].data(), this->mUsedClusters[iLayer].size() * sizeof(uint8_t), cudaHostRegisterPortable)); + mPinnedUsedClusters.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mUsedClustersDevice[layer]), this->mUsedClusters[layer].size() * sizeof(unsigned char), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemsetAsync(mUsedClustersDevice[layer], 0, this->mUsedClusters[layer].size() * sizeof(unsigned char), mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mUsedClustersDeviceArray[layer], &mUsedClustersDevice[layer], sizeof(unsigned char*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -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->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mROFramesClustersDevice.data(), nLayers * sizeof(int*), cudaHostRegisterPortable)); - mPinnedROFramesClusters.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mROFramesClusters[iLayer].data(), this->mROFramesClusters[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); - mPinnedROFramesClusters.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mROFramesClusters[iLayer].data(), this->mROFramesClusters[iLayer].size() * sizeof(int), cudaHostRegisterPortable)); + mPinnedROFramesClusters.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mROFramesClustersDevice[layer]), this->mROFramesClusters[layer].size() * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(mROFramesClustersDevice[layer], this->mROFramesClusters[layer].data(), this->mROFramesClusters[layer].size() * sizeof(int), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mROFramesClustersDeviceArray[layer], &mROFramesClustersDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -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->getExtAllocator()); - GPUChkErrS(cudaHostRegister(mTrackingFrameInfoDevice.data(), nLayers * sizeof(TrackingFrameInfo*), cudaHostRegisterPortable)); - mPinnedTrackingFrameInfo.set(nLayers); - for (auto iLayer{0}; iLayer < nLayers; ++iLayer) { - GPUChkErrS(cudaHostRegister(this->mTrackingFrameInfo[iLayer].data(), this->mTrackingFrameInfo[iLayer].size() * sizeof(TrackingFrameInfo), cudaHostRegisterPortable)); - mPinnedTrackingFrameInfo.set(iLayer); + 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) { + GPUChkErrS(cudaHostRegister(this->mTrackingFrameInfo[iLayer].data(), this->mTrackingFrameInfo[iLayer].size() * sizeof(TrackingFrameInfo), cudaHostRegisterPortable)); + mPinnedTrackingFrameInfo.set(iLayer); + } } } } -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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mTrackingFrameInfoDevice[layer]), this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(mTrackingFrameInfoDevice[layer], this->mTrackingFrameInfo[layer].data(), this->mTrackingFrameInfo[layer].size() * sizeof(TrackingFrameInfo), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); GPUChkErrS(cudaMemcpyAsync(&mTrackingFrameInfoDeviceArray[layer], &mTrackingFrameInfoDevice[layer], sizeof(TrackingFrameInfo*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } } -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->getExtAllocator()); - } - GPUChkErrS(cudaMemcpy(mMultMaskDevice, this->mMultiplicityCutMask.data(), this->mMultiplicityCutMask.size() * sizeof(uint8_t), cudaMemcpyHostToDevice)); + 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(const int iteration) +template +void TimeFrameGPU::loadVertices() { - if (!iteration) { + { 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->getExtAllocator()); - 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->getExtAllocator()); + 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) +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() { - if (!iteration) { - allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), (nLayers - 1) * sizeof(int*), this->getExtAllocator()); + { + allocMem(reinterpret_cast(&mTrackletsLUTDeviceArray), MaxTransitions * sizeof(int*), this->hasFrameworkAllocator()); } } -template -void TimeFrameGPU::createTrackletsLUTDevice(const int iteration, const int layer) +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->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mTrackletsLUTDevice[layer]), ncls * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator()); GPUChkErrS(cudaMemcpyAsync(&mTrackletsLUTDeviceArray[layer], &mTrackletsLUTDevice[layer], sizeof(int*), cudaMemcpyHostToDevice, mGpuStreams[layer].get())); } 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->getExtAllocator()); + 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->getExtAllocator()); + 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); - allocMemAsync(reinterpret_cast(&mNeighboursIndexTablesDevice[layer]), (mNCells[layer] + 1) * sizeof(int), mGpuStreams[layer], this->getExtAllocator()); + allocMemAsync(reinterpret_cast(&mNeighboursIndexTablesDevice[layer]), (mNCells[layer] + 1) * sizeof(int), mGpuStreams[layer], this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); 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->getExtAllocator()); // We need one element more to move exc -> inc + 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->getExtAllocator()); - allocMemAsync(reinterpret_cast(&mNeighboursIndexTablesDevice[iLayer]), (this->mCells[iLayer].size() + 1) * sizeof(int), mGpuStreams[iLayer], this->getExtAllocator()); // accessory for the neigh. finding. + 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->getExtAllocator()); + 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->getExtAllocator()); - 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->getExtAllocator()); - 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->getExtAllocator()); - 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->getExtAllocator()); - 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->getExtAllocator()); - GPUChkErrS(cudaHostRegister(seeds.data(), seeds.size() * sizeof(CellSeedN), cudaHostRegisterPortable)); - 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->getExtAllocator()); - 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->getExtAllocator()); + 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(bounded_vector& seeds) +template +void TimeFrameGPU::createTrackITSExtDevice(const size_t nSeeds) { GPUTimer timer("reserving tracks"); - mTrackITSExt = bounded_vector(seeds.size(), {}, this->getMemoryPool().get()); - GPULog("gpu-allocation: reserving {} tracks, for {:.2f} MB.", seeds.size(), seeds.size() * sizeof(o2::its::TrackITSExt) / constants::MB); - allocMem(reinterpret_cast(&mTrackITSExtDevice), seeds.size() * sizeof(o2::its::TrackITSExt), this->getExtAllocator()); - GPUChkErrS(cudaMemset(mTrackITSExtDevice, 0, seeds.size() * sizeof(o2::its::TrackITSExt))); - GPUChkErrS(cudaHostRegister(mTrackITSExt.data(), seeds.size() * sizeof(o2::its::TrackITSExt), cudaHostRegisterPortable)); + mNTracks = 0; + GPUChkErrS(cudaMemcpy(&mNTracks, mTrackSeedsLUTDevice + nSeeds, sizeof(int), cudaMemcpyDeviceToHost)); + GPULog("gpu-allocation: reserving {} tracks, for {:.2f} MB.", mNTracks, mNTracks * sizeof(o2::its::TrackITSExt) / constants::MB); + mTrackITSExt = bounded_vector(mNTracks, {}, this->getMemoryPool().get()); + allocMem(reinterpret_cast(&mTrackITSExtDevice), mNTracks * sizeof(o2::its::TrackITSExt), this->hasFrameworkAllocator(), (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + GPUChkErrS(cudaMemset(mTrackITSExtDevice, 0, mNTracks * sizeof(o2::its::TrackITSExt))); } -template -void TimeFrameGPU::downloadCellsDevice() +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(bounded_vector& seeds) +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, seeds.size() * sizeof(o2::its::TrackITSExt), cudaMemcpyDeviceToHost)); - GPUChkErrS(cudaHostUnregister(mTrackITSExt.data())); - GPUChkErrS(cudaHostUnregister(seeds.data())); + 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"); @@ -521,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); @@ -543,55 +662,97 @@ void TimeFrameGPU::unregisterHostMemory(const int maxLayers) checkedUnregisterArray(mPinnedROFramesClusters, mROFramesClustersDevice); } -template -void TimeFrameGPU::initialise(const int iteration, - const TrackingParameters& trkParam, - const int maxLayers, - IndexTableUtilsN* utils, - const TimeFrameGPUParameters* gpuParam) +namespace detail +{ +template +constexpr uint64_t makeIterTag() +{ + static_assert(I < 10); + constexpr char tag[] = {'I', 'T', 'S', 'I', 'T', 'E', 'R', char('0' + I), '\0'}; + return qStr2Tag(tag); +} +template +constexpr auto makeIterTags(std::index_sequence) +{ + return std::array{makeIterTag()...}; +} +constexpr auto kIterTags = makeIterTags(std::make_index_sequence{}); +} // namespace detail + +template +void TimeFrameGPU::pushMemoryStack(const int iteration) { - mGpuStreams.resize(nLayers); - o2::its::TimeFrame::initialise(iteration, trkParam, maxLayers); + // 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) +{ + // pop all memory on the stack from this iteration + (this->mExternalAllocator)->popTagOffStack(detail::kIterTags[iteration]); +} + +template +void TimeFrameGPU::initialise(const TrackingParameters& trkParam, int 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 6a824de851fed..141d558712e6d 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackerTraitsGPU.cxx @@ -10,406 +10,417 @@ // 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); + mTimeFrameGPU->initialise(this->mTrkParams[iteration], NLayers, iteration); - // on default stream - mTimeFrameGPU->loadVertices(iteration); - mTimeFrameGPU->loadIndexTableUtils(iteration); - mTimeFrameGPU->loadMultiplicityCutMask(iteration); - mTimeFrameGPU->createUsedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersDeviceArray(iteration); - mTimeFrameGPU->createUnsortedClustersDeviceArray(iteration); - mTimeFrameGPU->createClustersIndexTablesArray(iteration); - mTimeFrameGPU->createTrackingFrameInfoDeviceArray(iteration); - mTimeFrameGPU->createROFrameClustersDeviceArray(iteration); - mTimeFrameGPU->createTrackletsLUTDeviceArray(iteration); - mTimeFrameGPU->createTrackletsBuffersArray(iteration); - mTimeFrameGPU->createCellsBuffersArray(iteration); - mTimeFrameGPU->createCellsLUTDeviceArray(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(), - mTimeFrameGPU->getDeviceArrayUsedClusters(), + (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), 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->getExternalAllocator(), - conf.nBlocksLayerTracklets[iteration], - conf.nThreadsLayerTracklets[iteration], + mTimeFrameGPU->getTransitionMSAngles(), + mTimeFrameGPU->getFrameworkAllocator(), 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(), - mTimeFrameGPU->getDeviceArrayUsedClusters(), + (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), mTimeFrameGPU->getDeviceArrayClustersIndexTables(), mTimeFrameGPU->getDeviceArrayTracklets(), mTimeFrameGPU->getDeviceTracklets(), 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->getExternalAllocator(), - conf.nBlocksLayerTracklets[iteration], - conf.nThreadsLayerTracklets[iteration], + mTimeFrameGPU->getTransitionMSAngles(), + mTimeFrameGPU->getFrameworkAllocator(), 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, - mTimeFrameGPU->getExternalAllocator(), - conf.nBlocksLayerCells[iteration], - conf.nThreadsLayerCells[iteration], + this->mTrkParams[iteration].LayerxX0, + mTimeFrameGPU->getFrameworkAllocator(), 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->getExternalAllocator(), - 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->getExternalAllocator()); } 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->getDeviceArrayUsedClusters(), - mTimeFrameGPU->getDeviceNeighboursAll(), - mTimeFrameGPU->getDeviceNeighboursLUTs(), + mTimeFrameGPU->getDeviceCells()[startCellTopologyId], + nullptr, + nullptr, + mTimeFrameGPU->getArrayNCells().data(), + (const uint8_t**)mTimeFrameGPU->getDeviceArrayUsedClusters(), + 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->getExternalAllocator(), - 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()) { LOGP(debug, "No track seeds found, skipping track finding"); continue; } - mTimeFrameGPU->createTrackITSExtDevice(trackSeeds); mTimeFrameGPU->loadTrackSeedsDevice(trackSeeds); - trackSeedHandler(mTimeFrameGPU->getDeviceTrackSeeds(), // CellSeed* trackSeeds - mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), // TrackingFrameInfo** foundTrackingFrameInfo - mTimeFrameGPU->getDeviceTrackITSExt(), // o2::its::TrackITSExt* tracks - this->mTrkParams[iteration].MinPt, // std::vector& minPtsHost, - trackSeeds.size(), // const size_t nSeeds - this->mBz, // const float Bz - startLevel, // const int startLevel, - this->mTrkParams[0].MaxChi2ClusterAttachment, // float maxChi2ClusterAttachment - this->mTrkParams[0].MaxChi2NDF, // float maxChi2NDF - mTimeFrameGPU->getDevicePropagator(), // const o2::base::Propagator* propagator - this->mTrkParams[0].CorrType, // o2::base::PropagatorImpl::MatCorrType - conf.nBlocksTracksSeeds[iteration], - conf.nThreadsTracksSeeds[iteration]); - - mTimeFrameGPU->downloadTrackITSExtDevice(trackSeeds); + // Since TrackITSExt is an enourmous class it is better to first count how many + // successfull fits we do and only then allocate + countTrackSeedHandler(mTimeFrameGPU->getDeviceTrackSeeds(), + mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), + mTimeFrameGPU->getDeviceArrayUnsortedClusters(), + mTimeFrameGPU->getDeviceTrackSeedsLUT(), + this->mTrkParams[iteration].LayerRadii, + this->mTrkParams[iteration].MinPt, + this->mTrkParams[iteration].LayerxX0, + trackSeeds.size(), + this->mBz, + startLevel, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].ReseedIfShorter, + this->mTrkParams[iteration].RepeatRefitOut, + this->mTrkParams[iteration].ShiftRefToCluster, + mTimeFrameGPU->getDevicePropagator(), + this->mTrkParams[iteration].CorrType, + mTimeFrameGPU->getFrameworkAllocator()); + mTimeFrameGPU->createTrackITSExtDevice(trackSeeds.size()); + computeTrackSeedHandler(mTimeFrameGPU->getDeviceTrackSeeds(), + mTimeFrameGPU->getDeviceArrayTrackingFrameInfo(), + mTimeFrameGPU->getDeviceArrayUnsortedClusters(), + mTimeFrameGPU->getDeviceTrackITSExt(), + mTimeFrameGPU->getDeviceTrackSeedsLUT(), + this->mTrkParams[iteration].LayerRadii, + this->mTrkParams[iteration].MinPt, + this->mTrkParams[iteration].LayerxX0, + trackSeeds.size(), + mTimeFrameGPU->getNTrackSeeds(), + this->mBz, + startLevel, + this->mTrkParams[iteration].MaxChi2ClusterAttachment, + this->mTrkParams[iteration].MaxChi2NDF, + this->mTrkParams[iteration].ReseedIfShorter, + this->mTrkParams[iteration].RepeatRefitOut, + this->mTrkParams[iteration].ShiftRefToCluster, + mTimeFrameGPU->getDevicePropagator(), + 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 94c6610ab9430..571afe08fc209 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu +++ b/Detectors/ITSMFT/ITS/tracking/GPU/cuda/TrackingKernels.cu @@ -31,10 +31,11 @@ #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" +#include "utils/strtag.h" // O2 track model #include "ReconstructionDataFormats/Track.h" @@ -46,176 +47,6 @@ namespace o2::its namespace gpu { -template -struct TypedAllocator { - using value_type = T; - using pointer = thrust::device_ptr; - using const_pointer = thrust::device_ptr; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - TypedAllocator() noexcept : mInternalAllocator(nullptr) {} - explicit TypedAllocator(ExternalAllocator* a) noexcept : mInternalAllocator(a) {} - - template - TypedAllocator(const TypedAllocator& o) noexcept : mInternalAllocator(o.mInternalAllocator) - { - } - - pointer allocate(size_type n) - { - void* raw = mInternalAllocator->allocate(n * sizeof(T)); - return thrust::device_pointer_cast(static_cast(raw)); - } - - void deallocate(pointer p, size_type n) noexcept - { - if (!p) { - return; - } - void* raw = thrust::raw_pointer_cast(p); - mInternalAllocator->deallocate(static_cast(raw), n * sizeof(T)); - } - - bool operator==(TypedAllocator const& o) const noexcept - { - return mInternalAllocator == o.mInternalAllocator; - } - bool operator!=(TypedAllocator const& o) const noexcept - { - return !(*this == o); - } - - private: - ExternalAllocator* mInternalAllocator; -}; - -GPUdii() int4 getEmptyBinsRect() -{ - return int4{0, 0, 0, 0}; -} - -template -GPUdii() const int4 getBinsRect(const Cluster& currentCluster, const int layerIndex, - const 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 getEmptyBinsRect(); - } - - 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() 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) -{ - for (int iLayer{start}; iLayer != end; iLayer += step) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - const TrackingFrameInfo& trackingHit = tfInfos[iLayer][track.getClusterIndex(iLayer)]; - 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; - } - 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 float ca = o2::gpu::CAMath::Cos(tf3.alphaTrackingFrame), sa = o2::gpu::CAMath::Sin(tf3.alphaTrackingFrame); - const float x1 = cluster1.xCoordinate * ca + cluster1.yCoordinate * sa; - const float y1 = -cluster1.xCoordinate * sa + cluster1.yCoordinate * ca; - const float z1 = cluster1.zCoordinate; - const float x2 = cluster2.xCoordinate * ca + cluster2.yCoordinate * sa; - const float y2 = -cluster2.xCoordinate * sa + cluster2.yCoordinate * ca; - const float z2 = cluster2.zCoordinate; - const float x3 = tf3.xTrackingFrame; - const float y3 = tf3.positionTrackingFrame[0]; - const float z3 = tf3.positionTrackingFrame[1]; - - const bool zeroField{o2::gpu::CAMath::Abs(bz) < o2::constants::math::Almost0}; - const float tgp = zeroField ? o2::gpu::CAMath::ATan2(y3 - y1, x3 - x1) : 1.f; - const float crv = zeroField ? 1.f : math_utils::computeCurvature(x3, y3, x2, y2, x1, y1); - const float snp = zeroField ? tgp / o2::gpu::CAMath::Sqrt(1.f + tgp * tgp) : crv * (x3 - math_utils::computeCurvatureCentreX(x3, y3, x2, y2, x1, y1)); - const float tgl12 = math_utils::computeTanDipAngle(x1, y1, x2, y2, z1, z2); - const float tgl23 = math_utils::computeTanDipAngle(x2, y2, x3, y3, z2, z3); - const float q2pt = zeroField ? 1.f / o2::track::kMostProbablePt : crv / (bz * o2::constants::math::B2C); - const float q2pt2 = crv * crv; - const float sg2q2pt = o2::track::kC1Pt2max * (q2pt2 > 0.0005 ? (q2pt2 < 1 ? q2pt2 : 1) : 0.0005); - return track::TrackParCov(tf3.xTrackingFrame, tf3.alphaTrackingFrame, - {y3, z3, snp, 0.5f * (tgl12 + tgl23), 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}); -} - -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; } @@ -253,241 +84,191 @@ 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); } }; -GPUdii() gpuSpan getPrimaryVertices(const int rof, - const int* roframesPV, - const int nROF, - const uint8_t* mask, - const Vertex* vertices) -{ - const int start_pv_id = roframesPV[rof]; - const int stop_rof = rof >= nROF - 1 ? nROF : rof + 1; - const size_t delta = mask[rof] ? roframesPV[stop_rof] - start_pv_id : 0; // return empty span if ROF is excluded - return gpuSpan(&vertices[start_pv_id], delta); -}; - -GPUdii() gpuSpan getPrimaryVertices(const int romin, - const int romax, - const int* roframesPV, - const int nROF, - const Vertex* vertices) -{ - const int start_pv_id = roframesPV[romin]; - const int stop_rof = romax >= nROF - 1 ? nROF : romax + 1; - return gpuSpan(&vertices[start_pv_id], roframesPV[stop_rof] - roframesPV[romin]); -}; - -GPUdii() gpuSpan getClustersOnLayer(const int rof, - const int totROFs, - const int layer, - const int** roframesClus, - const Cluster** clusters) -{ - if (rof < 0 || rof >= totROFs) { - return gpuSpan(); - } - const int start_clus_id{roframesClus[layer][rof]}; - const int stop_rof = rof >= totROFs - 1 ? totROFs : rof + 1; - const unsigned int delta = roframesClus[layer][stop_rof] - start_clus_id; - return gpuSpan(&(clusters[layer][start_clus_id]), delta); -} - -template -GPUg() void fitTrackSeedsKernel( - CellSeed* trackSeeds, +template +GPUg() void __launch_bounds__(256, 1) fitTrackSeedsKernel( + 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, - float maxChi2ClusterAttachment, - float maxChi2NDF, + 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) { for (int iCurrentTrackSeedIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentTrackSeedIndex < nSeeds; iCurrentTrackSeedIndex += blockDim.x * gridDim.x) { - auto& seed = trackSeeds[iCurrentTrackSeedIndex]; - - TrackITSExt temporaryTrack{seed}; - temporaryTrack.resetCovariance(); - temporaryTrack.setChi2(0); - auto& clusters = seed.getClusters(); - for (int iL{0}; iL < nLayers; ++iL) { - temporaryTrack.setExternalClusterIndex(iL, clusters[iL], clusters[iL] != constants::UnusedIndex); - } - 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 - if (!fitSuccess) { - continue; + if constexpr (!initRun) { + if (seedLUT[iCurrentTrackSeedIndex] == seedLUT[iCurrentTrackSeedIndex + 1]) { + continue; + } } - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - temporaryTrack.resetCovariance(); - 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 - if (!fitSuccess || temporaryTrack.getPt() < minPts[nLayers - temporaryTrack.getNClusters()]) { - 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; + } } - tracks[iCurrentTrackSeedIndex] = temporaryTrack; } } -template -GPUg() void computeLayerCellNeighboursKernel( - CellSeed** cellSeedArray, - int* neighboursLUT, - int* neighboursIndexTable, +template +GPUg() void __launch_bounds__(256, 1) computeLayerCellNeighboursKernel( + 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) { - 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 -GPUg() void computeLayerCellsKernel( +template +GPUg() void __launch_bounds__(256, 1) computeLayerCellsKernel( const Cluster** sortedClusters, const Cluster** unsortedClusters, const TrackingFrameInfo** tfInfo, 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}; // 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) { - const Tracklet& currentTracklet = tracklets[layer][iCurrentTrackletIndex]; + if constexpr (!initRun) { + if (cellsLUTs[cellTopologyId][iCurrentTrackletIndex] == cellsLUTs[cellTopologyId][iCurrentTrackletIndex + 1]) { + continue; + } + } + 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; } @@ -495,7 +276,7 @@ GPUg() void computeLayerCellsKernel( break; } - if (!track.correctForMaterial(layerxX0[layer + iC], layerxX0[layer] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(layerxX0[layers[iC]], layerxX0[layers[iC]] * constants::Radl * constants::Rho, true)) { break; } @@ -513,64 +294,76 @@ GPUg() void 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; - } } } + if constexpr (initRun) { + cellsLUTs[cellTopologyId][iCurrentTrackletIndex] = foundCells; + } } } -template -GPUg() void computeLayerTrackletsMultiROFKernel( - const IndexTableUtils* utils, - const uint8_t* multMask, - const int layerIndex, - const int startROF, - const int endROF, - const int totalROFs, - const int deltaROF, +template +GPUg() void __launch_bounds__(256, 1) computeLayerTrackletsMultiROFKernel( + 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; } - auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs, layerIndex, ROFClusters, clusters); + const auto& rofOverlap = rofOverlaps.getOverlap(fromLayer, toLayer, pivotROF); + if (!rofOverlap.getEntries()) { + continue; + } + + auto clustersCurrentLayer = getClustersOnLayer(pivotROF, totalROFs0, fromLayer, ROFClusters, clusters); if (clustersCurrentLayer.empty()) { continue; } @@ -579,12 +372,12 @@ GPUg() void 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; } } @@ -592,7 +385,10 @@ GPUg() void 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; } @@ -602,8 +398,8 @@ GPUg() void 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}; @@ -612,35 +408,42 @@ GPUg() void 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; } @@ -652,66 +455,91 @@ GPUg() void computeLayerTrackletsMultiROFKernel( } } -GPUg() void compileTrackletsLookupTableKernel(const Tracklet* tracklets, - int* trackletsLookUpTable, - const int nTracklets) +GPUg() void __launch_bounds__(256, 1) compileTrackletsLookupTableKernel( + const Tracklet* tracklets, + int* trackletsLookUpTable, + const int nTracklets) { for (int currentTrackletIndex = blockIdx.x * blockDim.x + threadIdx.x; currentTrackletIndex < nTracklets; currentTrackletIndex += blockDim.x * gridDim.x) { atomicAdd(&trackletsLookUpTable[tracklets[currentTrackletIndex].firstClusterIndex], 1); } } -template -GPUg() void processNeighboursKernel(const int layer, - const int level, - CellSeed** allCellSeeds, - CellSeed* currentCellSeeds, - const int* currentCellIds, - const unsigned int nCurrentCells, - CellSeed* updatedCellSeeds, - int* updatedCellsIds, - 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, - const TrackingFrameInfo** foundTrackingFrameInfo, - const float bz, - const float maxChi2ClusterAttachment, - const o2::base::Propagator* propagator, - const o2::base::PropagatorF::MatCorrType matCorrType) +template +GPUg() void __launch_bounds__(256, 1) processNeighboursKernel( + const int defaultCellTopologyId, + const int level, + CellSeed** allCellSeeds, + CurrentSeed* currentCellSeeds, + const int* currentCellIds, + const int* currentCellTopologyIds, + const unsigned int nCurrentCells, + 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 + 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}; // 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]) { + continue; + } + } 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; @@ -722,7 +550,7 @@ GPUg() void processNeighboursKernel(const int layer, } 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; } } @@ -738,11 +566,15 @@ GPUg() void processNeighboursKernel(const int layer, 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++; @@ -750,37 +582,19 @@ GPUg() void processNeighboursKernel(const int layer, } } -GPUhi() void allocateMemory(void** p, size_t bytes, cudaStream_t stream = nullptr, ExternalAllocator* alloc = nullptr) -{ - if (alloc) { - *p = alloc->allocate(bytes); - } else { - GPUChkErrS(cudaMallocAsync(p, bytes, stream)); - } -} - -GPUhi() void deallocateMemory(void* p, size_t bytes, cudaStream_t stream = nullptr, ExternalAllocator* alloc = nullptr) -{ - if (alloc) { - alloc->deallocate(reinterpret_cast(p), bytes); - } else { - GPUChkErrS(cudaFreeAsync(p, stream)); - } -} } // 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, @@ -788,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, @@ -820,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, @@ -855,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, @@ -887,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, @@ -919,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, @@ -960,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, @@ -1076,45 +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, @@ -1122,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, @@ -1145,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, @@ -1178,81 +996,151 @@ 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 trackSeedHandler(CellSeed* trackSeeds, - const TrackingFrameInfo** foundTrackingFrameInfo, - o2::its::TrackITSExt* tracks, - std::vector& minPtsHost, - const unsigned int nSeeds, - const float bz, - const int startLevel, - float maxChi2ClusterAttachment, - float maxChi2NDF, - const o2::base::Propagator* propagator, - const o2::base::PropagatorF::MatCorrType matCorrType, - const int nBlocks, - const int nThreads) +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, + 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) { + // TODO: the minPts&layerRadii is transfered twice + // we should allocate this in constant memory and stop these + // small transferes! thrust::device_vector minPts(minPtsHost); - gpu::fitTrackSeedsKernel<<>>( - trackSeeds, // CellSeed* - foundTrackingFrameInfo, // TrackingFrameInfo** - tracks, // TrackITSExt* - thrust::raw_pointer_cast(&minPts[0]), // const float* minPts, - nSeeds, // const unsigned int - bz, // const float - startLevel, // const int - maxChi2ClusterAttachment, // float - maxChi2NDF, // float - propagator, // const o2::base::Propagator* - matCorrType); // o2::base::PropagatorF::MatCorrType + thrust::device_vector layerRadii(layerRadiiHost); + thrust::device_vector layerxX0(layerxX0Host); + gpu::fitTrackSeedsKernel<<<60, 256>>>( + trackSeeds, // CellSeed* + foundTrackingFrameInfo, // TrackingFrameInfo** + unsortedClusters, // Cluster** + nullptr, // TrackITSExt* + 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 + maxChi2ClusterAttachment, // float + maxChi2NDF, // float + reseedIfShorter, // int + repeatRefitOut, // bool + shiftRefToCluster, // bool + propagator, // const o2::base::Propagator* + matCorrType); // o2::base::PropagatorF::MatCorrType + auto sync_policy = THRUST_NAMESPACE::par(gpu::TypedAllocator(alloc)); + thrust::exclusive_scan(sync_policy, seedLUT, seedLUT + nSeeds + 1, seedLUT); +} + +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, + 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) +{ + thrust::device_vector minPts(minPtsHost); + thrust::device_vector layerRadii(layerRadiiHost); + thrust::device_vector layerxX0(layerxX0Host); + gpu::fitTrackSeedsKernel<<<60, 256>>>( + trackSeeds, // CellSeed* + foundTrackingFrameInfo, // TrackingFrameInfo** + unsortedClusters, // Cluster** + tracks, // TrackITSExt* + 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 + maxChi2ClusterAttachment, // float + maxChi2NDF, // float + reseedIfShorter, // int + repeatRefitOut, // bool + shiftRefToCluster, // bool + propagator, // const o2::base::Propagator* + matCorrType); // o2::base::PropagatorF::MatCorrType + auto sync_policy = THRUST_NAMESPACE::par(gpu::TypedAllocator(alloc)); thrust::device_ptr tr_ptr(tracks); - thrust::sort(tr_ptr, tr_ptr + nSeeds, gpu::compare_track_chi2()); - GPUChkErrS(cudaStreamSynchronize(gpu::Stream::DefaultStream)); + thrust::sort(sync_policy, tr_ptr, tr_ptr + nTracks, gpu::compare_track_chi2()); } /// 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, @@ -1260,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, @@ -1295,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, @@ -1315,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, @@ -1335,85 +1219,286 @@ 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); - -template void trackSeedHandler(CellSeed<7>* trackSeeds, - const TrackingFrameInfo** foundTrackingFrameInfo, - o2::its::TrackITSExt* tracks, - std::vector& minPtsHost, - const unsigned int nSeeds, - const float bz, - const int startLevel, - float maxChi2ClusterAttachment, - float maxChi2NDF, - const o2::base::Propagator* propagator, - const o2::base::PropagatorF::MatCorrType matCorrType, - const int nBlocks, - const int nThreads); + o2::its::ExternalAllocator* alloc); +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, + 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<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, + 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); + +/// 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 90d654a26a43d..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexerTraitsGPU.cxx +++ /dev/null @@ -1,227 +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 -#include -#include -#include -#include - -#ifdef VTX_DEBUG -#include "TTree.h" -#include "TFile.h" -#endif - -#include "ITStrackingGPU/VertexingKernels.h" -#include "ITStrackingGPU/VertexerTraitsGPU.h" - -namespace o2::its -{ - -void VertexerTraitsGPU::initialise(const TrackingParameters& trackingParams, const int iteration) -{ - mTimeFrameGPU->initialise(0, trackingParams, 3, &mIndexTableUtils, &mTfGPUParams); -} - -void VertexerTraitsGPU::updateVertexingParameters(const std::vector& vrtPar, const TimeFrameGPUParameters& tfPar) -{ - mVrtParams = vrtPar; - mTfGPUParams = tfPar; - mIndexTableUtils.setTrackingParameters(vrtPar[0]); - for (auto& par : mVrtParams) { - par.phiSpan = static_cast(std::ceil(mIndexTableUtils.getNphiBins() * par.phiCut / o2::constants::math::TwoPI)); - par.zSpan = static_cast(std::ceil(par.zCut * mIndexTableUtils.getInverseZCoordinate(0))); - } -} - -void VertexerTraitsGPU::computeTracklets(const int iteration) -{ - if (!mTimeFrameGPU->getClusters().size()) { - return; - } - // std::vector threads(mTimeFrameGPU->getNChunks()); - // for (int chunkId{0}; chunkId < mTimeFrameGPU->getNChunks(); ++chunkId) { - // int rofPerChunk{mTimeFrameGPU->mNrof / (int)mTimeFrameGPU->getNChunks()}; - // mTimeFrameGPU->getVerticesInChunks()[chunkId].clear(); - // mTimeFrameGPU->getNVerticesInChunks()[chunkId].clear(); - // mTimeFrameGPU->getLabelsInChunks()[chunkId].clear(); - // auto doVertexReconstruction = [&, chunkId, rofPerChunk]() -> void { - // auto offset = chunkId * rofPerChunk; - // auto maxROF = offset + rofPerChunk; - // while (offset < maxROF) { - // auto rofs = mTimeFrameGPU->loadChunkData(chunkId, offset, maxROF); - // RANGE("chunk_gpu_vertexing", 1); - // // gpu::GpuTimer timer{offset, mTimeFrameGPU->getStream(chunkId).get()}; - // // timer.Start("vtTrackletFinder"); - // gpu::trackleterKernelMultipleRof<<getStream(chunkId).get()>>>( - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(0), // const Cluster* clustersNextLayer, // 0 2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(1), // const Cluster* clustersCurrentLayer, // 1 1 - // mTimeFrameGPU->getDeviceROframesClusters(0), // const int* sizeNextLClusters, - // mTimeFrameGPU->getDeviceROframesClusters(1), // const int* sizeCurrentLClusters, - // mTimeFrameGPU->getChunk(chunkId).getDeviceIndexTables(0), // const int* nextIndexTables, - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(0), // Tracklet* Tracklets, - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(0), // int* foundTracklets, - // mTimeFrameGPU->getDeviceIndexTableUtils(), // const IndexTableUtils* utils, - // offset, // const unsigned int startRofId, - // rofs, // const unsigned int rofSize, - // mVrtParams.phiCut, // const float phiCut, - // mVrtParams.maxTrackletsPerCluster); // const size_t maxTrackletsPerCluster = 1e2 - - // gpu::trackleterKernelMultipleRof<<getStream(chunkId).get()>>>( - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(2), // const Cluster* clustersNextLayer, // 0 2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(1), // const Cluster* clustersCurrentLayer, // 1 1 - // mTimeFrameGPU->getDeviceROframesClusters(2), // const int* sizeNextLClusters, - // mTimeFrameGPU->getDeviceROframesClusters(1), // const int* sizeCurrentLClusters, - // mTimeFrameGPU->getChunk(chunkId).getDeviceIndexTables(2), // const int* nextIndexTables, - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(1), // Tracklet* Tracklets, - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(1), // int* foundTracklets, - // mTimeFrameGPU->getDeviceIndexTableUtils(), // const IndexTableUtils* utils, - // offset, // const unsigned int startRofId, - // rofs, // const unsigned int rofSize, - // mVrtParams.phiCut, // const float phiCut, - // mVrtParams.maxTrackletsPerCluster); // const size_t maxTrackletsPerCluster = 1e2 - - // gpu::trackletSelectionKernelMultipleRof<<getStream(chunkId).get()>>>( - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(0), // const Cluster* clusters0, // Clusters on layer 0 - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(1), // const Cluster* clusters1, // Clusters on layer 1 - // mTimeFrameGPU->getDeviceROframesClusters(0), // const int* sizeClustersL0, // Number of clusters on layer 0 per ROF - // mTimeFrameGPU->getDeviceROframesClusters(1), // const int* sizeClustersL1, // Number of clusters on layer 1 per ROF - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(0), // Tracklet* tracklets01, // Tracklets on layer 0-1 - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(1), // Tracklet* tracklets12, // Tracklets on layer 1-2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(0), // const int* nFoundTracklets01, // Number of tracklets found on layers 0-1 - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(1), // const int* nFoundTracklet12, // Number of tracklets found on layers 1-2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceUsedTracklets(), // unsigned char* usedTracklets, // Used tracklets - // mTimeFrameGPU->getChunk(chunkId).getDeviceLines(), // Line* lines, // Lines - // mTimeFrameGPU->getChunk(chunkId).getDeviceNFoundLines(), // int* nFoundLines, // Number of found lines - // mTimeFrameGPU->getChunk(chunkId).getDeviceNExclusiveFoundLines(), // int* nExclusiveFoundLines, // Number of found lines exclusive scan - // offset, // const unsigned int startRofId, // Starting ROF ID - // rofs, // const unsigned int rofSize, // Number of ROFs to consider - // mVrtParams.maxTrackletsPerCluster, // const int maxTrackletsPerCluster = 1e2, // Maximum number of tracklets per cluster - // mVrtParams.tanLambdaCut, // const float tanLambdaCut = 0.025f, // Cut on tan lambda - // mVrtParams.phiCut); // const float phiCut = 0.002f) // Cut on phi - - // discardResult(cub::DeviceScan::ExclusiveSum(mTimeFrameGPU->getChunk(chunkId).getDeviceCUBTmpBuffer(), - // mTimeFrameGPU->getChunk(chunkId).getTimeFrameGPUParameters()->tmpCUBBufferSize, - // mTimeFrameGPU->getChunk(chunkId).getDeviceNFoundLines(), - // mTimeFrameGPU->getChunk(chunkId).getDeviceNExclusiveFoundLines(), - // mTimeFrameGPU->getTotalClustersPerROFrange(offset, rofs, 1), - // mTimeFrameGPU->getStream(chunkId).get())); - - // // Reset used tracklets - // checkGPUError(cudaMemsetAsync(mTimeFrameGPU->getChunk(chunkId).getDeviceUsedTracklets(), - // false, - // sizeof(unsigned char) * mVrtParams.maxTrackletsPerCluster * mTimeFrameGPU->getTotalClustersPerROFrange(offset, rofs, 1), - // mTimeFrameGPU->getStream(chunkId).get()), - // __FILE__, __LINE__); - - // gpu::trackletSelectionKernelMultipleRof<<getStream(chunkId).get()>>>( - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(0), // const Cluster* clusters0, // Clusters on layer 0 - // mTimeFrameGPU->getChunk(chunkId).getDeviceClusters(1), // const Cluster* clusters1, // Clusters on layer 1 - // mTimeFrameGPU->getDeviceROframesClusters(0), // const int* sizeClustersL0, // Number of clusters on layer 0 per ROF - // mTimeFrameGPU->getDeviceROframesClusters(1), // const int* sizeClustersL1, // Number of clusters on layer 1 per ROF - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(0), // Tracklet* tracklets01, // Tracklets on layer 0-1 - // mTimeFrameGPU->getChunk(chunkId).getDeviceTracklets(1), // Tracklet* tracklets12, // Tracklets on layer 1-2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(0), // const int* nFoundTracklets01, // Number of tracklets found on layers 0-1 - // mTimeFrameGPU->getChunk(chunkId).getDeviceNTrackletCluster(1), // const int* nFoundTracklet12, // Number of tracklets found on layers 1-2 - // mTimeFrameGPU->getChunk(chunkId).getDeviceUsedTracklets(), // unsigned char* usedTracklets, // Used tracklets - // mTimeFrameGPU->getChunk(chunkId).getDeviceLines(), // Line* lines, // Lines - // mTimeFrameGPU->getChunk(chunkId).getDeviceNFoundLines(), // int* nFoundLines, // Number of found lines - // mTimeFrameGPU->getChunk(chunkId).getDeviceNExclusiveFoundLines(), // int* nExclusiveFoundLines, // Number of found lines exclusive scan - // offset, // const unsigned int startRofId, // Starting ROF ID - // rofs, // const unsigned int rofSize, // Number of ROFs to consider - // mVrtParams.maxTrackletsPerCluster, // const int maxTrackletsPerCluster = 1e2, // Maximum number of tracklets per cluster - // mVrtParams.tanLambdaCut, // const float tanLambdaCut = 0.025f, // Cut on tan lambda - // mVrtParams.phiCut); // const float phiCut = 0.002f) // Cut on phi - - // int nClusters = mTimeFrameGPU->getTotalClustersPerROFrange(offset, rofs, 1); - // int lastFoundLines; - // std::vector exclusiveFoundLinesHost(nClusters + 1); - - // // Obtain whole exclusive sum including nCluster+1 element (nCluster+1)th element is the total number of found lines. - // checkGPUError(cudaMemcpyAsync(exclusiveFoundLinesHost.data(), mTimeFrameGPU->getChunk(chunkId).getDeviceNExclusiveFoundLines(), (nClusters) * sizeof(int), cudaMemcpyDeviceToHost, mTimeFrameGPU->getStream(chunkId).get())); - // checkGPUError(cudaMemcpyAsync(&lastFoundLines, mTimeFrameGPU->getChunk(chunkId).getDeviceNFoundLines() + nClusters - 1, sizeof(int), cudaMemcpyDeviceToHost, mTimeFrameGPU->getStream(chunkId).get())); - // exclusiveFoundLinesHost[nClusters] = exclusiveFoundLinesHost[nClusters - 1] + lastFoundLines; - - // std::vector lines(exclusiveFoundLinesHost[nClusters]); - - // checkGPUError(cudaMemcpyAsync(lines.data(), mTimeFrameGPU->getChunk(chunkId).getDeviceLines(), sizeof(Line) * lines.size(), cudaMemcpyDeviceToHost, mTimeFrameGPU->getStream(chunkId).get())); - // checkGPUError(cudaStreamSynchronize(mTimeFrameGPU->getStream(chunkId).get())); - - // // Compute vertices - // std::vector clusterLines; - // std::vector usedLines; - // for (int rofId{0}; rofId < rofs; ++rofId) { - // auto rof = offset + rofId; - // auto clustersL1offsetRof = mTimeFrameGPU->getROframeClusters(1)[rof] - mTimeFrameGPU->getROframeClusters(1)[offset]; // starting cluster offset for this ROF - // auto nClustersL1Rof = mTimeFrameGPU->getROframeClusters(1)[rof + 1] - mTimeFrameGPU->getROframeClusters(1)[rof]; // number of clusters for this ROF - // auto linesOffsetRof = exclusiveFoundLinesHost[clustersL1offsetRof]; // starting line offset for this ROF - // auto nLinesRof = exclusiveFoundLinesHost[clustersL1offsetRof + nClustersL1Rof] - linesOffsetRof; - // gsl::span linesInRof(lines.data() + linesOffsetRof, static_cast::size_type>(nLinesRof)); - - // usedLines.resize(linesInRof.size(), false); - // usedLines.assign(linesInRof.size(), false); - // clusterLines.clear(); - // clusterLines.reserve(nClustersL1Rof); - // computeVerticesInRof(rof, - // linesInRof, - // usedLines, - // clusterLines, - // mTimeFrameGPU->getBeamXY(), - // mTimeFrameGPU->getVerticesInChunks()[chunkId], - // mTimeFrameGPU->getNVerticesInChunks()[chunkId], - // mTimeFrameGPU, - // mTimeFrameGPU->hasMCinformation() ? &mTimeFrameGPU->getLabelsInChunks()[chunkId] : nullptr); - // } - // offset += rofs; - // } - // }; - // // Do work - // threads[chunkId] = std::thread(doVertexReconstruction); - // } - // for (auto& thread : threads) { - // thread.join(); - // } - // for (int chunkId{0}; chunkId < mTimeFrameGPU->getNChunks(); ++chunkId) { - // int start{0}; - // for (int rofId{0}; rofId < mTimeFrameGPU->getNVerticesInChunks()[chunkId].size(); ++rofId) { - // gsl::span rofVerts{mTimeFrameGPU->getVerticesInChunks()[chunkId].data() + start, static_cast::size_type>(mTimeFrameGPU->getNVerticesInChunks()[chunkId][rofId])}; - // mTimeFrameGPU->addPrimaryVertices(rofVerts); - // if (mTimeFrameGPU->hasMCinformation()) { - // mTimeFrameGPU->getVerticesLabels().emplace_back(); - // // TODO: add MC labels - // } - // start += mTimeFrameGPU->getNVerticesInChunks()[chunkId][rofId]; - // } - // } - // mTimeFrameGPU->wipe(3); - // } -} - -void VertexerTraitsGPU::computeTrackletMatching(const int iteration) -{ -} - -void VertexerTraitsGPU::computeVertices(const int iteration) -{ -} - -void VertexerTraitsGPU::computeVerticesHist() -{ -} - -VertexerTraits* createVertexerTraitsGPU() -{ - return new VertexerTraitsGPU; -} -} // 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 126e799efce5d..0000000000000 --- a/Detectors/ITSMFT/ITS/tracking/GPU/cuda/VertexingKernels.cu +++ /dev/null @@ -1,572 +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 "GPUCommonHelpers.h" - -namespace o2 -{ -namespace its -{ -using math_utils::getNormalizedPhi; - -namespace gpu -{ -template -void trackletFinderHandler(const Cluster* clustersNextLayer, // 0 2 - const Cluster* clustersCurrentLayer, // 1 1 - const int* sizeNextLClusters, - const int* sizeCurrentLClusters, - const int* nextIndexTables, - Tracklet* Tracklets, - int* foundTracklets, - const IndexTableUtils* utils, - const unsigned int startRofId, - const unsigned int rofSize, - const float phiCut, - const unsigned int maxTrackletsPerCluster, - const int nBlocks, - const int nThreads) -{ - gpu::trackleterKernelMultipleRof<<>>( - clustersNextLayer, // const Cluster* clustersNextLayer, // 0 2 - clustersCurrentLayer, // const Cluster* clustersCurrentLayer, // 1 1 - sizeNextLClusters, // const int* sizeNextLClusters, - sizeCurrentLClusters, // const int* sizeCurrentLClusters, - nextIndexTables, // const int* nextIndexTables, - Tracklets, // Tracklet* Tracklets, - foundTracklets, // int* foundTracklets, - utils, // const IndexTableUtils* utils, - startRofId, // const unsigned int startRofId, - rofSize, // const unsigned int rofSize, - phiCut, // const float phiCut, - maxTrackletsPerCluster); // const unsigned int maxTrackletsPerCluster = 1e2 -} -/* - -GPUd() const int4 getBinsRect(const Cluster& currentCluster, const int layerIndex, - const float z1, float maxdeltaz, float maxdeltaphi) -{ - const float zRangeMin = z1 - maxdeltaz; - const float phiRangeMin = currentCluster.phi - maxdeltaphi; - const float zRangeMax = z1 + maxdeltaz; - const float phiRangeMax = currentCluster.phi + maxdeltaphi; - - if (zRangeMax < -LayersZCoordinate()[layerIndex + 1] || - zRangeMin > LayersZCoordinate()[layerIndex + 1] || zRangeMin > zRangeMax) { - - return getEmptyBinsRect(); - } - - return int4{o2::gpu::GPUCommonMath::Max(0, getZBinIndex(layerIndex + 1, zRangeMin)), - getPhiBinIndex(phiRangeMin), - o2::gpu::GPUCommonMath::Min(ZBins - 1, getZBinIndex(layerIndex + 1, zRangeMax)), - getPhiBinIndex(phiRangeMax)}; -} - -template -GPUd() void printOnThread(const unsigned int tId, const char* str, Args... args) -{ - if (blockIdx.x * blockDim.x + threadIdx.x == tId) { - printf(str, args...); - } -} - -template -GPUd() void printOnBlock(const unsigned int bId, const char* str, Args... args) -{ - if (blockIdx.x == bId && threadIdx.x == 0) { - printf(str, args...); - } -} - -GPUg() void printBufferOnThread(const int* v, unsigned int size, const int len = 150, const unsigned int tId = 0) -{ - if (blockIdx.x * blockDim.x + threadIdx.x == tId) { - for (int i{0}; i < size; ++i) { - if (!(i % len)) { - printf("\n start: ===>%d/%d\t", i, (int)size); - } - printf("%d\t", v[i]); - } - printf("\n"); - } -} - -GPUg() void printBufferOnThreadF(const float* v, unsigned int size, const unsigned int tId = 0) -{ - if (blockIdx.x * blockDim.x + threadIdx.x == tId) { - printf("vector :"); - for (int i{0}; i < size; ++i) { - printf("%.9f\t", v[i]); - } - printf("\n"); - } -} - -GPUg() void resetTrackletsKernel(Tracklet* tracklets, const int nTracklets) -{ - for (int iCurrentLayerClusterIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentLayerClusterIndex < nTracklets; iCurrentLayerClusterIndex += blockDim.x * gridDim.x) { - new (tracklets + iCurrentLayerClusterIndex) Tracklet{}; - } -} - -GPUg() void dumpFoundTrackletsKernel(const Tracklet* tracklets, const int* nTracklet, const unsigned int nClustersMiddleLayer, const int maxTrackletsPerCluster) -{ - for (int iCurrentLayerClusterIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentLayerClusterIndex < nClustersMiddleLayer; iCurrentLayerClusterIndex += blockDim.x * gridDim.x) { - const int stride{iCurrentLayerClusterIndex * maxTrackletsPerCluster}; - for (int iTracklet{0}; iTracklet < nTracklet[iCurrentLayerClusterIndex]; ++iTracklet) { - auto& t = tracklets[stride + iTracklet]; - t.dump(); - } - } -} - -GPUg() void dumpMaximaKernel(const cub::KeyValuePair* tmpVertexBins, const int threadId) -{ - if (blockIdx.x * blockDim.x + threadIdx.x == threadId) { - printf("XmaxBin: %d at index: %d | YmaxBin: %d at index: %d | ZmaxBin: %d at index: %d\n", - tmpVertexBins[0].value, tmpVertexBins[0].key, - tmpVertexBins[1].value, tmpVertexBins[1].key, - tmpVertexBins[2].value, tmpVertexBins[2].key); - } -} - -template -GPUg() void trackleterKernelSingleRof( - const Cluster* clustersNextLayer, // 0 2 - const Cluster* clustersCurrentLayer, // 1 1 - const int sizeNextLClusters, - const int sizeCurrentLClusters, - const int* indexTableNext, - const float phiCut, - Tracklet* Tracklets, - int* foundTracklets, - const IndexTableUtils* utils, - const short rofId, - const unsigned int maxTrackletsPerCluster = 1e2) -{ - const int phiBins{utils->getNphiBins()}; - const int zBins{utils->getNzBins()}; - // loop on layer1 clusters - for (int iCurrentLayerClusterIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentLayerClusterIndex < sizeCurrentLClusters; iCurrentLayerClusterIndex += blockDim.x * gridDim.x) { - if (iCurrentLayerClusterIndex < sizeCurrentLClusters) { - unsigned int storedTracklets{0}; - const unsigned int stride{iCurrentLayerClusterIndex * maxTrackletsPerCluster}; - 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) { - int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; - if (phiBinsNum < 0) { - phiBinsNum += phiBins; - } - // loop on phi bins next layer - for (unsigned int iPhiBin{(unsigned int)selectedBinsRect.y}, iPhiCount{0}; iPhiCount < (unsigned int)phiBinsNum; 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 < sizeNextLClusters; ++iNextLayerClusterIndex) { - const Cluster& nextCluster = clustersNextLayer[iNextLayerClusterIndex]; - if (o2::gpu::GPUCommonMath::Abs(currentCluster.phi - nextCluster.phi) < phiCut) { - if (storedTracklets < maxTrackletsPerCluster) { - if constexpr (Mode == TrackletMode::Layer0Layer1) { - new (Tracklets + stride + storedTracklets) Tracklet{iNextLayerClusterIndex, iCurrentLayerClusterIndex, nextCluster, currentCluster, rofId, rofId}; - } else { - new (Tracklets + stride + storedTracklets) Tracklet{iCurrentLayerClusterIndex, iNextLayerClusterIndex, currentCluster, nextCluster, rofId, rofId}; - } - ++storedTracklets; - } - } - } - } - } - foundTracklets[iCurrentLayerClusterIndex] = storedTracklets; - if (storedTracklets >= maxTrackletsPerCluster) { - printf("gpu tracklet finder: some lines will be left behind for cluster %d. valid: %u max: %zu\n", iCurrentLayerClusterIndex, storedTracklets, maxTrackletsPerCluster); - } - } - } -} - -template -GPUg() void trackleterKernelMultipleRof( - const Cluster* clustersNextLayer, // 0 2 - const Cluster* clustersCurrentLayer, // 1 1 - const int* sizeNextLClusters, - const int* sizeCurrentLClusters, - const int* nextIndexTables, - Tracklet* Tracklets, - int* foundTracklets, - const IndexTableUtils* utils, - const short startRofId, - const short rofSize, - const float phiCut, - const unsigned int maxTrackletsPerCluster = 1e2) -{ - const int phiBins{utils->getNphiBins()}; - const int zBins{utils->getNzBins()}; - for (auto iRof{blockIdx.x}; iRof < rofSize; iRof += gridDim.x) { - short rof = static_cast(iRof) + startRofId; - auto* clustersNextLayerRof = clustersNextLayer + (sizeNextLClusters[rof] - sizeNextLClusters[startRofId]); - auto* clustersCurrentLayerRof = clustersCurrentLayer + (sizeCurrentLClusters[rof] - sizeCurrentLClusters[startRofId]); - auto nClustersNextLayerRof = sizeNextLClusters[rof + 1] - sizeNextLClusters[rof]; - auto nClustersCurrentLayerRof = sizeCurrentLClusters[rof + 1] - sizeCurrentLClusters[rof]; - auto* indexTableNextRof = nextIndexTables + iRof * (phiBins * zBins + 1); - auto* TrackletsRof = Tracklets + (sizeCurrentLClusters[rof] - sizeCurrentLClusters[startRofId]) * maxTrackletsPerCluster; - auto* foundTrackletsRof = foundTracklets + (sizeCurrentLClusters[rof] - sizeCurrentLClusters[startRofId]); - - // single rof loop on layer1 clusters - for (int iCurrentLayerClusterIndex = threadIdx.x; iCurrentLayerClusterIndex < nClustersCurrentLayerRof; iCurrentLayerClusterIndex += blockDim.x) { - unsigned int storedTracklets{0}; - const unsigned int stride{iCurrentLayerClusterIndex * maxTrackletsPerCluster}; - const Cluster& currentCluster = clustersCurrentLayerRof[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) { - int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; - if (phiBinsNum < 0) { - phiBinsNum += phiBins; - } - // loop on phi bins next layer - for (unsigned int iPhiBin{(unsigned int)selectedBinsRect.y}, iPhiCount{0}; iPhiCount < (unsigned int)phiBinsNum; iPhiBin = ++iPhiBin == phiBins ? 0 : iPhiBin, iPhiCount++) { - const int firstBinIndex{utils->getBinIndex(selectedBinsRect.x, iPhiBin)}; - const int firstRowClusterIndex{indexTableNextRof[firstBinIndex]}; - const int maxRowClusterIndex{indexTableNextRof[firstBinIndex + zBins]}; - // loop on clusters next layer - for (int iNextLayerClusterIndex{firstRowClusterIndex}; iNextLayerClusterIndex < maxRowClusterIndex && iNextLayerClusterIndex < nClustersNextLayerRof; ++iNextLayerClusterIndex) { - const Cluster& nextCluster = clustersNextLayerRof[iNextLayerClusterIndex]; - if (o2::gpu::GPUCommonMath::Abs(smallestAngleDifference(currentCluster.phi, nextCluster.phi)) < phiCut) { - if (storedTracklets < maxTrackletsPerCluster) { - if constexpr (Mode == TrackletMode::Layer0Layer1) { - new (TrackletsRof + stride + storedTracklets) Tracklet{iNextLayerClusterIndex, iCurrentLayerClusterIndex, nextCluster, currentCluster, rof, rof}; - } else { - new (TrackletsRof + stride + storedTracklets) Tracklet{iCurrentLayerClusterIndex, iNextLayerClusterIndex, currentCluster, nextCluster, rof, rof}; - } - ++storedTracklets; - } - } - } - } - } - foundTrackletsRof[iCurrentLayerClusterIndex] = storedTracklets; - // if (storedTracklets >= maxTrackletsPerCluster && storedTracklets - maxTrackletsPerCluster < 5) { - // printf("gpu tracklet finder: some lines will be left behind for cluster %d in rof: %d. valid: %u max: %lu (suppressing after 5 msgs)\n", iCurrentLayerClusterIndex, rof, storedTracklets, maxTrackletsPerCluster); - // } - } - } -} - -template -GPUg() void trackletSelectionKernelSingleRof( - const Cluster* clusters0, - const Cluster* clusters1, - const unsigned int nClustersMiddleLayer, - Tracklet* tracklets01, - Tracklet* tracklets12, - const int* nFoundTracklet01, - const int* nFoundTracklet12, - unsigned char* usedTracklets, - Line* lines, - int* nFoundLines, - int* nExclusiveFoundLines, - const int maxTrackletsPerCluster = 1e2, - const float tanLambdaCut = 0.025f, - const float phiCut = 0.002f) -{ - for (int iCurrentLayerClusterIndex = blockIdx.x * blockDim.x + threadIdx.x; iCurrentLayerClusterIndex < nClustersMiddleLayer; iCurrentLayerClusterIndex += blockDim.x * gridDim.x) { - const int stride{iCurrentLayerClusterIndex * maxTrackletsPerCluster}; - int validTracklets{0}; - for (int iTracklet12{0}; iTracklet12 < nFoundTracklet12[iCurrentLayerClusterIndex]; ++iTracklet12) { - for (int iTracklet01{0}; iTracklet01 < nFoundTracklet01[iCurrentLayerClusterIndex] && validTracklets < maxTrackletsPerCluster; ++iTracklet01) { - const float deltaTanLambda{o2::gpu::GPUCommonMath::Abs(tracklets01[stride + iTracklet01].tanLambda - tracklets12[stride + iTracklet12].tanLambda)}; - const float deltaPhi{o2::gpu::GPUCommonMath::Abs(smallestAngleDifference(tracklets01[stride + iTracklet01].phi, tracklets12[stride + iTracklet12].phi))}; - if (!usedTracklets[stride + iTracklet01] && deltaTanLambda < tanLambdaCut && deltaPhi < phiCut && validTracklets != maxTrackletsPerCluster) { - usedTracklets[stride + iTracklet01] = true; - if constexpr (!initRun) { - new (lines + nExclusiveFoundLines[iCurrentLayerClusterIndex] + validTracklets) Line{tracklets01[stride + iTracklet01], clusters0, clusters1}; - } - ++validTracklets; - } - } - } - if constexpr (initRun) { - nFoundLines[iCurrentLayerClusterIndex] = validTracklets; - if (validTracklets >= maxTrackletsPerCluster) { - printf("gpu tracklet selection: some lines will be left behind for cluster %d. valid: %d max: %d\n", iCurrentLayerClusterIndex, validTracklets, maxTrackletsPerCluster); - } - } - } -} - -template -GPUg() void trackletSelectionKernelMultipleRof( - const Cluster* clusters0, // Clusters on layer 0 - const Cluster* clusters1, // Clusters on layer 1 - const int* sizeClustersL0, // Number of clusters on layer 0 per ROF - const int* sizeClustersL1, // Number of clusters on layer 1 per ROF - Tracklet* tracklets01, // Tracklets on layer 0-1 - Tracklet* tracklets12, // Tracklets on layer 1-2 - const int* nFoundTracklets01, // Number of tracklets found on layers 0-1 - const int* nFoundTracklets12, // Number of tracklets found on layers 1-2 - unsigned char* usedTracklets, // Used tracklets - Line* lines, // Lines - int* nFoundLines, // Number of found lines - int* nExclusiveFoundLines, // Number of found lines exclusive scan - const unsigned int startRofId, // Starting ROF ID - const unsigned int rofSize, // Number of ROFs to consider - const int maxTrackletsPerCluster = 1e2, // Maximum number of tracklets per cluster - const float tanLambdaCut = 0.025f, // Cut on tan lambda - const float phiCut = 0.002f) // Cut on phi -{ - for (unsigned int iRof{blockIdx.x}; iRof < rofSize; iRof += gridDim.x) { - auto rof = iRof + startRofId; - auto* clustersL0Rof = clusters0 + (sizeClustersL0[rof] - sizeClustersL0[startRofId]); - auto clustersL1offsetRof = sizeClustersL1[rof] - sizeClustersL1[startRofId]; - auto* clustersL1Rof = clusters1 + clustersL1offsetRof; - auto nClustersL1Rof = sizeClustersL1[rof + 1] - sizeClustersL1[rof]; - auto* tracklets01Rof = tracklets01 + clustersL1offsetRof * maxTrackletsPerCluster; - auto* tracklets12Rof = tracklets12 + clustersL1offsetRof * maxTrackletsPerCluster; - auto* foundTracklets01Rof = nFoundTracklets01 + clustersL1offsetRof; - auto* foundTracklets12Rof = nFoundTracklets12 + clustersL1offsetRof; - auto* usedTrackletsRof = usedTracklets + clustersL1offsetRof * maxTrackletsPerCluster; - auto* foundLinesRof = nFoundLines + clustersL1offsetRof; - int* nExclusiveFoundLinesRof = nullptr; - if constexpr (!initRun) { - nExclusiveFoundLinesRof = nExclusiveFoundLines + clustersL1offsetRof; - } - for (int iClusterIndexLayer1 = threadIdx.x; iClusterIndexLayer1 < nClustersL1Rof; iClusterIndexLayer1 += blockDim.x) { - const int stride{iClusterIndexLayer1 * maxTrackletsPerCluster}; - int validTracklets{0}; - for (int iTracklet12{0}; iTracklet12 < foundTracklets12Rof[iClusterIndexLayer1]; ++iTracklet12) { - for (int iTracklet01{0}; iTracklet01 < foundTracklets01Rof[iClusterIndexLayer1] && validTracklets < maxTrackletsPerCluster; ++iTracklet01) { - const float deltaTanLambda{o2::gpu::GPUCommonMath::Abs(tracklets01Rof[stride + iTracklet01].tanLambda - tracklets12Rof[stride + iTracklet12].tanLambda)}; - const float deltaPhi{o2::gpu::GPUCommonMath::Abs(tracklets01Rof[stride + iTracklet01].phi - tracklets12Rof[stride + iTracklet12].phi)}; - if (!usedTrackletsRof[stride + iTracklet01] && deltaTanLambda < tanLambdaCut && deltaPhi < phiCut && validTracklets != maxTrackletsPerCluster) { - usedTrackletsRof[stride + iTracklet01] = true; - if constexpr (!initRun) { - new (lines + nExclusiveFoundLinesRof[iClusterIndexLayer1] + validTracklets) Line{tracklets01Rof[stride + iTracklet01], clustersL0Rof, clustersL1Rof}; - } - ++validTracklets; - } - } - } - if constexpr (initRun) { - foundLinesRof[iClusterIndexLayer1] = validTracklets; - // if (validTracklets >= maxTrackletsPerCluster) { - // printf("gpu tracklet selection: some lines will be left behind for cluster %d. valid: %d max: %d\n", iClusterIndexLayer1, validTracklets, maxTrackletsPerCluster); - // } - } - } - } // rof loop -} - -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 gpu -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt b/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt index dd83669311a54..e28fe04c06772 100644 --- a/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/tracking/GPU/hip/CMakeLists.txt @@ -13,14 +13,12 @@ if(HIP_ENABLED) message(STATUS "Building ITS HIP tracker") set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} -fgpu-rdc") # set(CMAKE_HIP_FLAGS "${CMAKE_HIP_FLAGS} -O0 -g -ggdb -fno-inline -fno-omit-frame-pointer -D__HIP_ENABLE_DEVICE_ASSERT__") + # 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 ac9f72089602d..3a03e9d145907 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/BoundedAllocator.h @@ -22,11 +22,18 @@ #include #include +#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: @@ -34,48 +41,83 @@ 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, + 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)); - return mUpstream->allocate(bytes, alignment); + + 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; } void do_deallocate(void* p, size_t bytes, size_t alignment) final { 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 @@ -83,99 +125,157 @@ 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) { - if (mUsedMemory > max) { - ++mCountThrow; - throw MemoryLimitExceeded(0, mUsedMemory, 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 = max; } - 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: - size_t mMaxMemory{std::numeric_limits::max()}; + std::atomic mMaxMemory{std::numeric_limits::max()}; std::atomic mCountThrow{0}; std::atomic mUsedMemory{0}; - std::pmr::memory_resource* mUpstream; + 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 using bounded_vector = std::pmr::vector; template -void deepVectorClear(std::vector& vec) +inline void deepVectorClear(std::vector& vec) { std::vector().swap(vec); } template -inline void deepVectorClear(bounded_vector& vec, BoundedMemoryResource* bmr = nullptr) +inline void deepVectorClear(bounded_vector& vec, std::pmr::memory_resource* mr = nullptr) { + std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource(); vec.~bounded_vector(); - if (bmr == nullptr) { - auto alloc = vec.get_allocator().resource(); - new (&vec) bounded_vector(alloc); - } else { - new (&vec) bounded_vector(bmr); - } + new (&vec) bounded_vector(std::pmr::polymorphic_allocator{tmr}); } template -void deepVectorClear(std::vector>& vec, BoundedMemoryResource* bmr = nullptr) +inline void deepVectorClear(std::vector>& vec, std::pmr::memory_resource* mr = nullptr) { for (auto& v : vec) { - deepVectorClear(v, bmr); + deepVectorClear(v, mr); } } template -void deepVectorClear(std::array, S>& arr, BoundedMemoryResource* bmr = nullptr) +inline void deepVectorClear(std::array, S>& arr, std::pmr::memory_resource* mr = nullptr) { for (size_t i{0}; i < S; ++i) { - deepVectorClear(arr[i], bmr); + deepVectorClear(arr[i], mr); } } template -void clearResizeBoundedVector(bounded_vector& vec, size_t size, BoundedMemoryResource* bmr, T def = T()) +inline void clearResizeBoundedVector(bounded_vector& vec, size_t sz, std::pmr::memory_resource* mr = nullptr, T def = T()) { + std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource(); vec.~bounded_vector(); - new (&vec) bounded_vector(size, def, bmr); + new (&vec) bounded_vector(sz, def, std::pmr::polymorphic_allocator{tmr}); } template -void clearResizeBoundedVector(std::vector>& vec, size_t size, BoundedMemoryResource* bmr) +inline void clearResizeBoundedVector(std::vector>& vec, size_t size, std::pmr::memory_resource* mr) { vec.clear(); vec.reserve(size); - for (size_t i{0}; i < size; ++i) { - vec.emplace_back(bmr); + for (size_t i = 0; i < size; ++i) { + vec.emplace_back(std::pmr::polymorphic_allocator>{mr}); } } template -void clearResizeBoundedArray(std::array, S>& arr, size_t size, BoundedMemoryResource* bmr, T def = T()) +inline void clearResizeBoundedArray(std::array, S>& arr, size_t size, std::pmr::memory_resource* mr = nullptr, T def = T()) { for (size_t i{0}; i < S; ++i) { - clearResizeBoundedVector(arr[i], size, bmr, def); + clearResizeBoundedVector(arr[i], size, mr, def); } } template -std::vector toSTDVector(const bounded_vector& b) +inline std::vector toSTDVector(const bounded_vector& b) { std::vector t(b.size()); std::copy(b.cbegin(), b.cend(), t.begin()); 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 dd96dc80f2926..34014d858648b 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/Cluster.h @@ -16,8 +16,10 @@ #ifndef TRACKINGITSU_INCLUDE_CACLUSTER_H_ #define TRACKINGITSU_INCLUDE_CACLUSTER_H_ +#include +#include "ITStracking/Constants.h" #include "GPUCommonRtypes.h" -#include "GPUCommonArray.h" +#include "GPUCommonDef.h" namespace o2::its { @@ -47,8 +49,8 @@ struct Cluster final { float zCoordinate{-999.f}; float phi{-999.f}; float radius{-999.f}; - int clusterId{-1}; - int indexTableBinIndex{-1}; + int clusterId{constants::UnusedIndex}; + int indexTableBinIndex{constants::UnusedIndex}; ClassDefNV(Cluster, 1); }; @@ -70,8 +72,8 @@ struct TrackingFrameInfo final { float zCoordinate{-999.f}; float xTrackingFrame{-999.f}; float alphaTrackingFrame{-999.f}; - std::array positionTrackingFrame = {-1., -1.}; - 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 2bfa0639ad5a2..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,77 +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 - 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}; - unsigned char StartLayerMask = 0x7F; - bool FindShortTracks = false; + 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 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 @@ -124,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 72857794c711e..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. // @@ -15,21 +15,54 @@ #ifndef TRACKINGITS_DEFINITIONS_H_ #define TRACKINGITS_DEFINITIONS_H_ -#include "ReconstructionDataFormats/Vertex.h" - -#ifdef CA_DEBUG -#define CA_DEBUGGER(x) x -#else -#define CA_DEBUGGER(x) \ - do { \ - } while (0) -#endif +#include +#include namespace o2::its { -using Vertex = o2::dataformats::Vertex>; +enum class TrackletMode { + Layer0Layer1 = 0, + Layer1Layer2 = 2 +}; + +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/ExternalAllocator.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ExternalAllocator.h index 1628bbc52776b..7d1e98736db2c 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ExternalAllocator.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/ExternalAllocator.h @@ -16,15 +16,71 @@ #ifndef TRACKINGITSU_INCLUDE_EXTERNALALLOCATOR_H_ #define TRACKINGITSU_INCLUDE_EXTERNALALLOCATOR_H_ +#include +#include "GPUO2ExternalUser.h" +#include "Base/GPUMemoryResource.h" + namespace o2::its { class ExternalAllocator { + using Type = std::underlying_type_t; + public: - virtual void* allocate(size_t) = 0; virtual void deallocate(char*, size_t) = 0; + virtual void* allocate(size_t) = 0; + void* allocate(size_t s, Type type) + { + auto old = mType; + mType = type; + void* p = allocate(s); + mType = old; + return p; + } + void* allocateStack(size_t s) + { + return allocate(s, (o2::gpu::GPUMemoryResource::MEMORY_GPU | o2::gpu::GPUMemoryResource::MEMORY_STACK)); + } + virtual void pushTagOnStack(uint64_t) = 0; + virtual void popTagOffStack(uint64_t) = 0; + + void setType(Type t) noexcept { mType = t; } + Type getType() const noexcept { return mType; } + + protected: + Type mType; }; + +class ExternalAllocatorAdaptor final : public std::pmr::memory_resource +{ + public: + explicit ExternalAllocatorAdaptor(ExternalAllocator* alloc) : mAlloc(alloc) {} + + protected: + void* do_allocate(size_t bytes, size_t alignment) override + { + void* p = mAlloc->allocate(bytes, o2::gpu::GPUMemoryResource::MemoryType::MEMORY_HOST); + if (!p) { + throw std::bad_alloc(); + } + return p; + } + + void do_deallocate(void* p, size_t bytes, size_t) override + { + mAlloc->deallocate(static_cast(p), bytes); + } + + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override + { + return this == &other; + } + + private: + ExternalAllocator* mAlloc; +}; + } // 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 a148049e50129..3fef2dc640cbc 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TimeFrame.h @@ -21,23 +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 @@ -63,64 +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(); - virtual ~TimeFrame(); + 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(); + 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; } @@ -133,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; } @@ -171,43 +200,36 @@ 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); + void setMemoryPool(std::shared_ptr pool); auto& getMemoryPool() const noexcept { return mMemoryPool; } bool checkMemory(unsigned long max) { return getArtefactsMemory() < max; } 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(); void computeTracletsPerClusterScans(); int& getNTrackletsROF(int rofId, int combId) { return mNTrackletsPerROF[combId][rofId]; } auto& getLines(int rofId) { return mLines[rofId]; } - int getNLinesTotal() const - { - return std::accumulate(mLines.begin(), mLines.end(), 0, [](int sum, const auto& l) { return sum + l.size(); }); - } + int getNLinesTotal() const noexcept { return mTotalLines; } + void setNLinesTotal(uint32_t a) noexcept { mTotalLines = a; } auto& getTrackletClusters(int rofId) { return mTrackletClusters[rofId]; } gsl::span getFoundTracklets(int rofId, int combId) const; gsl::span getFoundTracklets(int rofId, int combId); @@ -216,43 +238,24 @@ 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; } float getBz() const { return mBz; } - void setExternalAllocator(ExternalAllocator* allocator) - { - if (mIsGPU) { - LOGP(debug, "Setting timeFrame allocator to external"); - mAllocator = allocator; - mExtAllocator = true; // to be removed - } else { - LOGP(fatal, "External allocator is currently only supported for GPU"); - } - } + /// State if memory will be externally managed by the GPU framework + ExternalAllocator* mExternalAllocator{nullptr}; + std::shared_ptr mExtMemoryPool; // host memory pool managed by the framework + auto getFrameworkAllocator() { return mExternalAllocator; }; + void setFrameworkAllocator(ExternalAllocator* ext); + bool hasFrameworkAllocator() const noexcept { return mExternalAllocator != nullptr; } + std::pmr::memory_resource* getMaybeFrameworkHostResource(bool forceHost = false) { return (hasFrameworkAllocator() && !forceHost) ? mExtMemoryPool.get() : mMemoryPool.get(); } - ExternalAllocator* getExternalAllocator() { return mAllocator; } - - virtual void setDevicePropagator(const o2::base::PropagatorImpl*) - { - return; - }; + // Propagator const o2::base::PropagatorImpl* getDevicePropagator() const { return mPropagatorDevice; } + virtual void setDevicePropagator(const o2::base::PropagatorImpl* /*unused*/) {}; template void addClusterToLayer(int layer, T&&... args); @@ -260,332 +263,244 @@ struct TimeFrame { void addTrackingFrameInfoToLayer(int layer, T&&... args); void addClusterExternalIndexToLayer(int layer, const int idx) { mClusterExternalIndices[layer].push_back(idx); } - void resetVectors(); - void resetTracklets(); - - /// 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; - - bool mIsGPU = false; - - 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; - // State if memory will be externally managed. - bool mExtAllocator = false; - ExternalAllocator* mAllocator = nullptr; - bool getExtAllocator() const { return mExtAllocator; } - - 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 virtual void wipe(); - private: - void prepareClusters(const TrackingParameters& trkParam, const int maxLayers = nLayers); + // interface + virtual bool isGPU() const noexcept { return false; } + virtual const char* getName() const noexcept { return "CPU"; } + + protected: + 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}; - unsigned int mNoVertexROF = 0; - bounded_vector mTotVertPerIteration; + uint32_t mTotalLines = 0; // \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 -{ - if (mPrimaryVertices.empty()) { - 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)}; -} - -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 +template +gsl::span TimeFrame::getPrimaryVertices(int layer, 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()) { + if (rofId < 0 || rofId >= getNrof(layer)) { 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)}; + const auto& entry = mROFVertexLookupTableView.getVertices(layer, rofId); + return {&mPrimaryVertices[entry.getFirstEntry()], static_cast::size_type>(entry.getEntries())}; } -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) +template +inline gsl::span TimeFrame::getNTrackletsCluster(int rofId, int combId) { - mRoadLabels[i].first = lab; - mRoadLabels[i].second = fake; -} - -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]}; @@ -593,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) { @@ -633,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) { @@ -693,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 642717bd09596..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,71 +45,71 @@ 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'; }); void setParameters(const std::vector& p) { mTrkParams = p; } - void setMemoryPool(std::shared_ptr& pool) { mMemoryPool = pool; } + 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 computeTracksMClabels(); 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 9d14bb91635a0..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,96 +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; } + 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 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); - - 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 e8d3692909d05..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,63 +84,32 @@ 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 - - bool createArtefactLabels{false}; // create on-the-fly labels for the artefacts + 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 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 - - O2ParamDef(TrackerParamConfig, "ITSCATrackerParam"); -}; - -struct ITSGpuTrackingParamConfig : public o2::conf::ConfigurableParamHelper { - static constexpr int MaxIter = TrackerParamConfig::MaxIter; + bool fataliseUponFailure = true; // granular management of the fatalisation in async mode - /// 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; + // 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 - /// Individual kernel launch parameter for each iteration - int nBlocksLayerTracklets[MaxIter] = {30, 30, 30, 30}; - int nThreadsLayerTracklets[MaxIter] = {256, 256, 256, 256}; - - int nBlocksLayerCells[MaxIter] = {30, 30, 30, 30}; - int nThreadsLayerCells[MaxIter] = {256, 256, 256, 256}; - - int nBlocksFindNeighbours[MaxIter] = {30, 30, 30, 30}; - int nThreadsFindNeighbours[MaxIter] = {256, 256, 256, 256}; - - int nBlocksProcessNeighbours[MaxIter] = {30, 30, 30, 30}; - int nThreadsProcessNeighbours[MaxIter] = {256, 256, 256, 256}; - - int nBlocksTracksSeeds[MaxIter] = {30, 30, 30, 30}; - int nThreadsTracksSeeds[MaxIter] = {256, 256, 256, 256}; - - O2ParamDef(ITSGpuTrackingParamConfig, "ITSGpuTrackingParam"); + O2ParamDef(TrackerParamConfig, "ITSCATrackerParam"); }; } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h index 787f299e15888..14c5d6a62e0ad 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/TrackingInterface.h @@ -24,8 +24,8 @@ #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsCalibration/MeanVertexObject.h" -#include "GPUDataTypes.h" -#include "GPUO2Interface.h" +#include "GPUDataTypesIO.h" +#include "GPUO2ExternalUser.h" #include "GPUChainITS.h" #include @@ -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 loadROF(gsl::span& trackROFspan, + 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 ab92e7c1a1523..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: @@ -52,13 +52,11 @@ class Vertexer auto& getVertParameters() const { return mTraits->getVertexingParameters(); } void setParameters(const std::vector& vertParams) { mVertParams = vertParams; } const auto& getParameters() const noexcept { return mVertParams; } - void setMemoryPool(std::shared_ptr& pool) { mMemoryPool = pool; } - - std::vector exportVertices(); - VertexerTraitsN* getTraits() const { return mTraits; }; + void setMemoryPool(std::shared_ptr pool) { mMemoryPool = pool; } 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 7da7617957179..daf8d708e1e23 100644 --- a/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h +++ b/Detectors/ITSMFT/ITS/tracking/include/ITStracking/VertexerTraits.h @@ -43,37 +43,23 @@ class MCCompLabel; namespace its { -enum class TrackletMode { - Layer0Layer1 = 0, - Layer1Layer2 = 2 -}; - -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(); @@ -87,9 +73,9 @@ class VertexerTraits virtual bool isGPU() const noexcept { return false; } virtual const char* getName() const noexcept { return "CPU"; } virtual bool usesMemoryPool() const noexcept { return true; } - void setMemoryPool(std::shared_ptr& pool) { mMemoryPool = pool; } + 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 { @@ -107,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())); } @@ -117,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 6af66b18a2878..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) { @@ -186,14 +203,25 @@ std::vector TrackingMode::getTrackingParameters(TrackingMode int lslot = tc.MaxTrackLength - ilg; p.MinPt[lslot] *= bFactor; } - - p.createArtefactLabels = tc.createArtefactLabels; + p.ReseedIfShorter = tc.reseedIfShorter; + p.RepeatRefitOut = tc.repeatRefitOut; + p.ShiftRefToCluster = tc.shiftRefToCluster; + p.CreateArtefactLabels = tc.createArtefactLabels; p.PrintMemory = tc.printMemory; p.MaxMemory = tc.maxMemory; p.DropTFUponFailure = tc.dropTFUponFailure; 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; @@ -209,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; @@ -219,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) { @@ -257,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 510c66e2420f1..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,94 +43,43 @@ constexpr float DefClusErrorCol = o2::itsmft::SegmentationAlpide::PitchCol * 0.5 constexpr float DefClusError2Row = DefClusErrorRow * DefClusErrorRow; constexpr float DefClusError2Col = DefClusErrorCol * DefClusErrorCol; -template -TimeFrame::TimeFrame() +template +void TimeFrame::addPrimaryVertex(const Vertex& vert) { - resetVectors(); -} - -template -TimeFrame::~TimeFrame() -{ - wipe(); -} - -template -void TimeFrame::addPrimaryVertices(const bounded_vector& vertices, const int iteration) -{ - 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) +template +void TimeFrame::loadROFrameData(gsl::span rofs, + gsl::span clusters, + gsl::span::iterator& pattIt, + const itsmft::TopologyDictionary* dict, + int layer, + const dataformats::MCTruthContainer* mcLabels) { - 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) -{ - resetROFrameData(); - 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); + + // 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()); + } - mNrof = 0; - clearResizeBoundedVector(mClusterSize, clusters.size(), mMemoryPool.get()); - for (auto& rof : rofs) { + 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) @@ -154,69 +100,97 @@ int TimeFrame::loadROFrameData(gsl::span rofs, 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].push_back(mUnsortedClusters[iL].size()); + // 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(); + } } - mNrof++; } - 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(int layer) +{ + 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::resetROFrameData() +template +void TimeFrame::prepareROFrameData(gsl::span clusters, int layer) { - for (int iLayer{0}; iLayer < nLayers; ++iLayer) { - deepVectorClear(mUnsortedClusters[iLayer], mMemoryPool.get()); - deepVectorClear(mTrackingFrameInfo[iLayer], mMemoryPool.get()); - deepVectorClear(mClusterExternalIndices[iLayer], mMemoryPool.get()); - clearResizeBoundedVector(mROFramesClusters[iLayer], 1, mMemoryPool.get(), 0); - - if (iLayer < 2) { - deepVectorClear(mTrackletsIndexROF[iLayer], mMemoryPool.get()); - deepVectorClear(mNTrackletsPerCluster[iLayer], mMemoryPool.get()); - deepVectorClear(mNTrackletsPerClusterSum[iLayer], mMemoryPool.get()); + 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; @@ -266,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(), mMemoryPool.get()); - clearResizeBoundedVector(mUsedClusters[iLayer], mUnsortedClusters[iLayer].size(), mMemoryPool.get()); - 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), mMemoryPool.get()); - 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]) { @@ -315,12 +301,26 @@ void TimeFrame::initialise(const int iteration, const TrackingParameter } } } - } + + 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}; @@ -331,87 +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]; - 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))); - 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]; } } @@ -420,272 +415,110 @@ 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::resetVectors() -{ - mMinR.fill(10000.); - mMaxR.fill(-1.); - for (int iLayers{nLayers}; iLayers--;) { - mClusters[iLayers].clear(); - mUnsortedClusters[iLayers].clear(); - mTrackingFrameInfo[iLayers].clear(); - mClusterExternalIndices[iLayers].clear(); - mUsedClusters[iLayers].clear(); - mROFramesClusters[iLayers].clear(); - mNClustersPerROF[iLayers].clear(); - } - for (int i{2}; i--;) { - mTrackletsIndexROF[i].clear(); - } -} - -template -void TimeFrame::resetTracklets() -{ - for (auto& trkl : mTracklets) { - deepVectorClear(trkl); - } - deepVectorClear(mTrackletsLookupTable); -} - -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; - auto initVector = [&](bounded_vector& vec) { - auto alloc = vec.get_allocator().resource(); - if (alloc != mMemoryPool.get()) { - vec = bounded_vector(mMemoryPool.get()); - } - }; - auto initArrays = [&](std::array, S>& arr) { - for (size_t i{0}; i < S; ++i) { - auto alloc = arr[i].get_allocator().resource(); - if (alloc != mMemoryPool.get()) { - arr[i] = bounded_vector(mMemoryPool.get()); - } - } + auto initVector = [&](bounded_vector& vec, bool useExternal = false) { + std::pmr::memory_resource* mr = (useExternal) ? mExtMemoryPool.get() : mMemoryPool.get(); + deepVectorClear(vec, mr); }; - auto initVectors = [&](std::vector>& vec) { - for (size_t i{0}; i < vec.size(); ++i) { - auto alloc = vec[i].get_allocator().resource(); - if (alloc != mMemoryPool.get()) { - vec[i] = bounded_vector(mMemoryPool.get()); - } + + auto initContainers = [&](Container& container, bool useExternal = false) { + for (auto& v : container) { + initVector(v, useExternal); } }; - initVector(mTotVertPerIteration); + // these will only reside on the host for the cpu part + initContainers(mClusterExternalIndices); + initContainers(mNTrackletsPerCluster); + initContainers(mNTrackletsPerClusterSum); + initContainers(mNClustersPerROF); initVector(mPrimaryVertices); - initVector(mROFramesPV); - initArrays(mClusters); - initArrays(mTrackingFrameInfo); - initArrays(mClusterExternalIndices); - initArrays(mROFramesClusters); - initArrays(mNTrackletsPerCluster); - initArrays(mNTrackletsPerClusterSum); - initArrays(mNClustersPerROF); - initArrays(mIndexTables); - initArrays(mUsedClusters); - initArrays(mUnsortedClusters); - initVector(mROFramesPV); - initVector(mPrimaryVertices); - initVector(mRoads); - initVector(mRoadLabels); - initVector(mMSangles); - initVector(mPhiCuts); + initVector(mTransitionPhiCuts); + initVector(mTransitionMSAngles); initVector(mPositionResolution); - initVector(mClusterSize); + initContainers(mClusterSize); initVector(mPValphaX); initVector(mBogusClusters); - initVector(mVerticesContributorLabels); - initArrays(mTrackletsIndexROF); - initVectors(mTracks); - initVectors(mTracklets); - initVectors(mCells); - initVectors(mCellsNeighbours); - initVectors(mCellsLookupTable); + initContainers(mTrackletsIndexROF); + initVector(mTracks); + initContainers(mTracklets); + initContainers(mCells); + initContainers(mCellsNeighbours); + initContainers(mCellsLookupTable); + // MC info (we don't know if we have MC) + initVector(mPrimaryVerticesLabels); + initContainers(mLinesLabels); + initContainers(mTrackletLabels); + initContainers(mCellLabels); + initVector(mTracksLabel); + // these will use possibly an externally provided allocator + initContainers(mClusters, hasFrameworkAllocator()); + initContainers(mUsedClusters, hasFrameworkAllocator()); + initContainers(mUnsortedClusters, hasFrameworkAllocator()); + initContainers(mIndexTables, hasFrameworkAllocator()); + initContainers(mTrackingFrameInfo, hasFrameworkAllocator()); + initContainers(mROFramesClusters, hasFrameworkAllocator()); +} + +template +void TimeFrame::setFrameworkAllocator(ExternalAllocator* ext) +{ + mExternalAllocator = ext; + mExtMemoryPool = std::make_shared(mExternalAllocator); } -template -void TimeFrame::wipe() +template +void TimeFrame::wipe() { - deepVectorClear(mUnsortedClusters); deepVectorClear(mTracks); deepVectorClear(mTracklets); deepVectorClear(mCells); - deepVectorClear(mRoads); deepVectorClear(mCellsNeighbours); + deepVectorClear(mCellsNeighboursTopology); deepVectorClear(mCellsLookupTable); - deepVectorClear(mTotVertPerIteration); deepVectorClear(mPrimaryVertices); - deepVectorClear(mClusters); deepVectorClear(mTrackletsLookupTable); - deepVectorClear(mTrackingFrameInfo); deepVectorClear(mClusterExternalIndices); - deepVectorClear(mROFramesClusters); deepVectorClear(mNTrackletsPerCluster); deepVectorClear(mNTrackletsPerClusterSum); deepVectorClear(mNClustersPerROF); - deepVectorClear(mIndexTables); - deepVectorClear(mUsedClusters); - deepVectorClear(mUnsortedClusters); - deepVectorClear(mROFramesPV); - deepVectorClear(mPrimaryVertices); - deepVectorClear(mRoads); - deepVectorClear(mMSangles); - deepVectorClear(mPhiCuts); + deepVectorClear(mTransitionPhiCuts); + deepVectorClear(mTransitionMSAngles); deepVectorClear(mPositionResolution); deepVectorClear(mClusterSize); deepVectorClear(mPValphaX); deepVectorClear(mBogusClusters); deepVectorClear(mTrackletsIndexROF); - deepVectorClear(mPrimaryVertices); deepVectorClear(mTrackletClusters); deepVectorClear(mLines); + // if we use the external host allocator then the assumption is that we + // don't clear the memory ourself + if (!hasFrameworkAllocator()) { + deepVectorClear(mClusters); + deepVectorClear(mUsedClusters); + deepVectorClear(mUnsortedClusters); + deepVectorClear(mIndexTables); + deepVectorClear(mTrackingFrameInfo); + deepVectorClear(mROFramesClusters); + } + // 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); } } template class TimeFrame<7>; +// ALICE3 upgrade +#ifdef ENABLE_UPGRADES +template class TimeFrame<11>; +#endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx index e8212f4ad53a1..f17d961fc7bb7 100644 --- a/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx +++ b/Detectors/ITSMFT/ITS/tracking/src/Tracker.cxx @@ -14,64 +14,52 @@ /// #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, "Exception: {}", err.what()); + 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()); + } if (mTrkParams[iteration].DropTFUponFailure) { mMemoryPool->print(); mTimeFrame->wipe(); ++mNumberOfDroppedTFs; - error("...Dropping Timeframe..."); + error(std::format("...Dropping TimeSlice {} (out of {} dropped {})...", mTimeSlice, mTimeFrameCounter, mNumberOfDroppedTFs)); } else { throw err; } @@ -80,168 +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->mIsGPU) ? 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; - } catch (...) { - error("Uncaught exception, all bets are off..."); + 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 + 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]}; + occurrences.clear(); - if (currentCellIndex == constants::UnusedIndex) { - if (isFirstRoadCell) { - continue; - } else { - break; - } + for (int iCluster = 0; iCluster < TrackITSExt::MaxClusters; ++iCluster) { + const int index = track.getClusterIndex(iCluster); + if (index == constants::UnusedIndex) { + continue; } - - 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}; - - 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; - } - - 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; @@ -250,110 +143,125 @@ 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>; +// ALICE3 upgrade +#ifdef ENABLE_UPGRADES +template class Tracker<11>; +#endif } // namespace o2::its diff --git a/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx b/Detectors/ITSMFT/ITS/tracking/src/TrackerTraits.cxx index 83210d474ed9f..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,172 +173,130 @@ 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( - tbb::blocked_range(0, mTrkParams[iteration].TrackletsPerRoad()), - [&](auto const& Layers) { - for (int iLayer{Layers.begin()}; iLayer < Layers.end(); ++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( - tbb::blocked_range(0, mTrkParams[iteration].TrackletsPerRoad()), - [&](const tbb::blocked_range& Layers) { - for (int iLayer = Layers.begin(); iLayer < Layers.end(); ++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()); - 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()); - } - } + 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(); + 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( - tbb::blocked_range(0, mTrkParams[iteration].TrackletsPerRoad()), - [&](const tbb::blocked_range& Layers) { - for (int iLayer = Layers.begin(); iLayer < Layers.end(); ++iLayer) { - for (auto& trk : mTimeFrame->getTracklets()[iLayer]) { - 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)) { - if (lab1 == lab2 && lab1.isValid()) { - label = lab1; - break; - } - } - if (label.isValid()) { - break; - } + 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()[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; } - mTimeFrame->getTrackletsLabel(iLayer).emplace_back(label); + } + if (label.isValid()) { + break; } } - }); + 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; @@ -369,7 +306,7 @@ void TrackerTraits::computeLayerCells(const int iteration) break; } - if (!track.correctForMaterial(mTrkParams[0].LayerxX0[iLayer + iC], mTrkParams[0].LayerxX0[iLayer] * constants::Radl * constants::Rho, true)) { + if (!track.correctForMaterial(mTrkParams[iteration].LayerxX0[hitLayer], mTrkParams[iteration].LayerxX0[hitLayer] * constants::Radl * constants::Rho, true)) { break; } @@ -386,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!"); } @@ -402,295 +342,268 @@ void TrackerTraits::computeLayerCells(const int iteration) return foundCells; }; - tbb::parallel_for( - tbb::blocked_range(0, mTrkParams[iteration].CellsPerRoad()), - [&](const tbb::blocked_range& Layers) { - for (int iLayer = Layers.begin(); iLayer < Layers.end(); ++iLayer) { - if (mTimeFrame->getTracklets()[iLayer + 1].empty() || - mTimeFrame->getTracklets()[iLayer].empty()) { - continue; - } + 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())}; - 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); - } - std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); - } else { - tbb::parallel_for( - tbb::blocked_range(0, currentLayerTrackletsNum), - [&](const tbb::blocked_range& Tracklets) { - for (int iTracklet = Tracklets.begin(); iTracklet < Tracklets.end(); ++iTracklet) { - perTrackletCount[iTracklet] = forTrackletCells(PassMode::TwoPassCount{}, iLayer, layerCells, iTracklet); - } - }); + 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{}, 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{}, cellTopologyId, layerCells, iTracklet); + }); - std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); - auto totalCells{perTrackletCount.back()}; - if (totalCells == 0) { - continue; - } - layerCells.resize(totalCells); - - tbb::parallel_for( - tbb::blocked_range(0, currentLayerTrackletsNum), - [&](const tbb::blocked_range& Tracklets) { - for (int iTracklet = Tracklets.begin(); iTracklet < Tracklets.end(); ++iTracklet) { - int offset = perTrackletCount[iTracklet]; - if (offset == perTrackletCount[iTracklet + 1]) { - continue; - } - forTrackletCells(PassMode::TwoPassInsert{}, iLayer, layerCells, iTracklet, offset); - } - }); - } + std::exclusive_scan(perTrackletCount.begin(), perTrackletCount.end(), perTrackletCount.begin(), 0); + auto totalCells{perTrackletCount.back()}; + if (totalCells == 0) { + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::fill(lut.begin(), lut.end(), 0); + continue; + } + layerCells.resize(totalCells); - if (iLayer > 0) { - auto& lut = mTimeFrame->getCellsLookupTable()[iLayer - 1]; - lut.resize(currentLayerTrackletsNum + 1); - std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); + tbb::parallel_for(0, currentLayerTrackletsNum, [&](const int iTracklet) { + int offset = perTrackletCount[iTracklet]; + if (offset == perTrackletCount[iTracklet + 1]) { + return; } + forTrackletCells(PassMode::TwoPassInsert{}, cellTopologyId, layerCells, iTracklet, offset); + }); + } + + auto& lut = mTimeFrame->getCellsLookupTable()[cellTopologyId]; + lut.resize(currentLayerTrackletsNum + 1); + std::copy_n(perTrackletCount.begin(), currentLayerTrackletsNum + 1, lut.begin()); + + 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()); } - }); + } + } }); - /// Create cells labels - if (mTimeFrame->hasMCinformation() && mTrkParams[iteration].createArtefactLabels) { - tbb::parallel_for( - tbb::blocked_range(0, mTrkParams[iteration].CellsPerRoad()), - [&](const tbb::blocked_range& Layers) { - for (int iLayer = Layers.begin(); iLayer < Layers.end(); ++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()); - } - } - }); + 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( - tbb::blocked_range(0, nCells), - [&](const tbb::blocked_range& Cells) { - for (int iCell = Cells.begin(); iCell < Cells.end(); ++iCell) { - perCellCount[iCell] = forCellNeighbour(PassMode::TwoPassCount{}, iCell); + 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); } - }); - - 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( - tbb::blocked_range(0, nCells), - [&](const tbb::blocked_range& Cells) { - for (int iCell = Cells.begin(); iCell < Cells.end(); ++iCell) { - int offset = perCellCount[iCell]; - if (offset == perCellCount[iCell + 1]) { - continue; - } - 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); - } - o2::gpu::CAMath::AtomicMax(mTimeFrame->getCells()[iLayer + 1][cellIdx].getLevelPtr(), 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()); @@ -699,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!"); } @@ -718,13 +633,9 @@ void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bou } } else { bounded_vector perCellCount(nCells + 1, 0, mMemoryPool.get()); - tbb::parallel_for( - tbb::blocked_range(0, nCells), - [&](const tbb::blocked_range& Cells) { - for (int iCell = Cells.begin(); iCell < Cells.end(); ++iCell) { - perCellCount[iCell] = forCellNeighbours(PassMode::TwoPassCount{}, iCell); - } - }); + tbb::parallel_for(0, nCells, [&](const int iCell) { + perCellCount[iCell] = forCellNeighbours(PassMode::TwoPassCount{}, iCell); + }); std::exclusive_scan(perCellCount.begin(), perCellCount.end(), perCellCount.begin(), 0); auto totalNeighbours{perCellCount.back()}; @@ -733,63 +644,66 @@ void TrackerTraits::processNeighbours(int iLayer, int iLevel, const bou } updatedCellSeeds.resize(totalNeighbours); updatedCellsIds.resize(totalNeighbours); + updatedCellsTopologyIds.resize(totalNeighbours); - tbb::parallel_for( - tbb::blocked_range(0, nCells), - [&](const tbb::blocked_range& Cells) { - for (int iCell = Cells.begin(); iCell < Cells.end(); ++iCell) { - int offset = perCellCount[iCell]; - if (offset == perCellCount[iCell + 1]) { - continue; - } - forCellNeighbours(PassMode::TwoPassInsert{}, iCell, offset); - } - }); + tbb::parallel_for(0, nCells, [&](const int iCell) { + int offset = perCellCount[iCell]; + if (offset == perCellCount[iCell + 1]) { + return; + } + forCellNeighbours(PassMode::TwoPassInsert{}, iCell, offset); + }); } }); - -#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) { - CA_DEBUGGER(std::cout << "Finding roads, iteration " << iteration << std::endl); - + bounded_vector> firstClusters(mTrkParams[iteration].NLayers, bounded_vector(mMemoryPool.get()), mMemoryPool.get()); + firstClusters.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) { - CA_DEBUGGER(std::cout << "\t > Processing level " << startLevel << std::endl); + 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].CellsPerRoad() - 1}; 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; } - CA_DEBUGGER(std::cout << "\t\t > Starting processing layer " << startLayer << std::endl); + 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)); @@ -804,37 +718,36 @@ void TrackerTraits::findRoads(const int iteration) bounded_vector tracks(mMemoryPool.get()); mTaskArena->execute([&] { auto forSeed = [&](auto Tag, int iSeed, int offset = 0) { - const auto& seed{trackSeeds[iSeed]}; - TrackITSExt temporaryTrack{seed}; - temporaryTrack.resetCovariance(); - temporaryTrack.setChi2(0); - for (int iL{0}; iL < nLayers; ++iL) { - temporaryTrack.setExternalClusterIndex(iL, seed.getCluster(iL), seed.getCluster(iL) != constants::UnusedIndex); - } - - bool fitSuccess = fitTrack(temporaryTrack, 0, mTrkParams[0].NLayers, 1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF); - if (!fitSuccess) { - return 0; - } - - temporaryTrack.getParamOut() = temporaryTrack.getParamIn(); - temporaryTrack.resetCovariance(); - temporaryTrack.setChi2(0); - fitSuccess = fitTrack(temporaryTrack, mTrkParams[0].NLayers - 1, -1, -1, mTrkParams[0].MaxChi2ClusterAttachment, mTrkParams[0].MaxChi2NDF, 50.f); - if (!fitSuccess || temporaryTrack.getPt() < mTrkParams[iteration].MinPt[mTrkParams[iteration].NLayers - temporaryTrack.getNClusters()]) { - return 0; - } - - 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!"); + 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!"); + } + return 1; } - return 1; + return 0; }; const int nSeeds = static_cast(trackSeeds.size()); @@ -843,14 +756,11 @@ 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( - tbb::blocked_range(0, nSeeds), - [&](const tbb::blocked_range& Seeds) { - for (int iSeed = Seeds.begin(); iSeed < Seeds.end(); ++iSeed) { - perSeedCount[iSeed] = forSeed(PassMode::TwoPassCount{}, iSeed); - } - }); + tbb::parallel_for(0, nSeeds, [&](const int iSeed) { + perSeedCount[iSeed] = forSeed(PassMode::TwoPassCount{}, iSeed); + }); std::exclusive_scan(perSeedCount.begin(), perSeedCount.end(), perSeedCount.begin(), 0); auto totalTracks{perSeedCount.back()}; @@ -859,406 +769,155 @@ void TrackerTraits::findRoads(const int iteration) } tracks.resize(totalTracks); - tbb::parallel_for( - tbb::blocked_range(0, nSeeds), - [&](const tbb::blocked_range& Seeds) { - for (int iSeed = Seeds.begin(); iSeed < Seeds.end(); ++iSeed) { - if (perSeedCount[iSeed] == perSeedCount[iSeed + 1]) { - continue; - } - forSeed(PassMode::TwoPassInsert{}, iSeed, perSeedCount[iSeed]); - } - }); + tbb::parallel_for(0, nSeeds, [&](const int iSeed) { + if (perSeedCount[iSeed] == perSeedCount[iSeed + 1]) { + return; + } + forSeed(PassMode::TwoPassInsert{}, iSeed, perSeedCount[iSeed]); + }); } 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}; - for (int iLayer{0}; iLayer < mTrkParams[0].NLayers; ++iLayer) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; - } - nShared += int(mTimeFrame->isClusterUsed(iLayer, track.getClusterIndex(iLayer))); - isFirstShared |= !iLayer && mTimeFrame->isClusterUsed(iLayer, track.getClusterIndex(iLayer)); - } - - if (nShared > mTrkParams[0].ClusterSharing) { - continue; - } + std::sort(tracks.begin(), tracks.end(), [](const auto& a, const auto& b) { + return track::isBetter(a, b); + }); - 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); - } + 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); + 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 ((mTrkParams[iteration].UseTrackFollowerMix || (mTrkParams[iteration].UseTrackFollowerBot && !success)) && track.getFirstClusterLayer() != 0) { - success = success || trackFollowing(&track, rof, false, iteration); + 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; } - 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)); - } - } - } - } -} - -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])) { + bool firstCls{true}, nominalCompatible{true}; + TimeEstBC nominalTS, expandedTS; + for (int iLayer{0}; iLayer < mTrkParams[iteration].NLayers; ++iLayer) { + if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { continue; } - if (!propagator->propagateTo(temporaryTrack, pvsXAlpha[iV][0], true)) { - 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; + } + } + if (!expandedTS.isCompatible(expandedROFTS)) { + LOGP(fatal, "TS {}+/-{} are incompatible with {}+/-{}, this should not happen!", expandedROFTS.getTimeStamp(), expandedROFTS.getTimeStampError(), expandedTS.getTimeStamp(), expandedTS.getTimeStampError()); } - bestTrack = temporaryTrack; - bestChi2 = chi2; + 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; - } - 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) -{ - auto propInstance = o2::base::Propagator::Instance(); + track.setUserField(0); + track.getParamOut().setUserField(0); + trks.emplace_back(track); - for (int iLayer{start}; iLayer != end; iLayer += step) { - if (track.getClusterIndex(iLayer) == constants::UnusedIndex) { - continue; + if (mTrkParams[iteration].AllowSharingFirstCluster) { + firstClusters[firstLayer].push_back(firstCluster); } - const TrackingFrameInfo& trackingHit = mTimeFrame->getTrackingFrameInfoOnLayer(iLayer)[track.getClusterIndex(iLayer)]; - - if (!track.rotate(trackingHit.alphaTrackingFrame)) { - return false; - } - - if (!propInstance->propagateToX(track, trackingHit.xTrackingFrame, getBz(), o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, mTrkParams[0].CorrType)) { - 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; - } - 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) +template +void TrackerTraits::markTracks(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].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(); + }); - 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; - } + auto areTracksSelected = [this, iteration](const TrackITSExt& t1, const TrackITSExt& t2) { + const auto t1FirstLayer{t1.getFirstClusterLayer()}, t2FirstLayer{t2.getFirstClusterLayer()}; + if (t1FirstLayer != t2FirstLayer) { + return false; } - - // 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; + if (mTimeFrame->getClusterROF(t1FirstLayer, t1.getClusterIndex(t1FirstLayer)) != mTimeFrame->getClusterROF(t2FirstLayer, t2.getClusterIndex(t2FirstLayer))) { + return false; } - - int phiBinsNum{selectedBinsRect.w - selectedBinsRect.y + 1}; - - if (phiBinsNum < 0) { - phiBinsNum += mTrkParams[iteration].PhiBins; + if (!math_utils::isPhiDifferenceBelow(t1.getPhi(), t2.getPhi(), mTrkParams[iteration].SharedClusterMaxDeltaPhi)) { + return false; } - - gsl::span layer1 = mTimeFrame->getClustersOnLayer(rof, iLayer); - if (layer1.empty()) { - continue; + if (std::abs(t1.getEta() - t2.getEta()) > mTrkParams[iteration].SharedClusterMaxDeltaEta) { + return false; } + if (mTrkParams[iteration].SharedClusterOppositeSign && t1.getSign() == t2.getSign()) { + return false; + } + return true; + }; - // 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); + 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(); } } } } - - 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; } -/// 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) -{ - float ca{-999.f}, sa{-999.f}; - 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 z1 = cluster1.zCoordinate; - const float x2 = cluster2.xCoordinate * ca + cluster2.yCoordinate * sa; - const float y2 = -cluster2.xCoordinate * sa + cluster2.yCoordinate * ca; - const float z2 = cluster2.zCoordinate; - const float x3 = tf3.xTrackingFrame; - const float y3 = tf3.positionTrackingFrame[0]; - const float z3 = tf3.positionTrackingFrame[1]; - float tgp{1.f}, crv{1.f}, snp{-999.f}, tgl12{-999.f}, tgl23{-999.f}, q2pt{1.f / track::kMostProbablePt}, q2pt2{1.f}, sg2q2pt{-999.f}; - if (mIsZeroField) { - tgp = o2::gpu::CAMath::ATan2(y3 - y1, x3 - x1); - snp = tgp / o2::gpu::CAMath::Sqrt(1.f + tgp * tgp); - } else { - crv = math_utils::computeCurvature(x3, y3, x2, y2, x1, y1); - snp = crv * (x3 - math_utils::computeCurvatureCentreX(x3, y3, x2, y2, x1, y1)); - q2pt = crv / (mBz * o2::constants::math::B2C); - q2pt2 = crv * crv; - } - tgl12 = math_utils::computeTanDipAngle(x1, y1, x2, y2, z1, z2); - tgl23 = math_utils::computeTanDipAngle(x2, y2, x3, y3, z2, z3); - sg2q2pt = track::kC1Pt2max * (q2pt2 > 0.0005f ? (q2pt2 < 1.f ? q2pt2 : 1.f) : 0.0005f); - return {tf3.xTrackingFrame, tf3.alphaTrackingFrame, {y3, z3, snp, 0.5f * (tgl12 + tgl23), 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 +template +void TrackerTraits::setNThreads(int n, std::shared_ptr& arena) { - return o2::base::Propagator::Instance()->getMatLUT() && (mTrkParams[0].CorrType == o2::base::PropagatorImpl::MatCorrType::USEMatCorrLUT); -} - -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) { @@ -1266,11 +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 f673d8f446350..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,53 +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(); + 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]); + } - gsl::span trackROFspan(trackROFvec); - loadROF(trackROFspan, compClusters, pattIt, labels); - pattIt = patterns.begin(); - std::vector savedROF; 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); @@ -194,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(); } @@ -325,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"); @@ -335,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); } } @@ -382,6 +467,7 @@ void ITSTrackingInterface::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) void ITSTrackingInterface::printSummary() const { + mVertexer->printSummary(); mTracker->printSummary(); } @@ -406,10 +492,16 @@ void ITSTrackingInterface::setTraitsFromProvider(VertexerTraitsN* vertexerTraits mVertexer->setMemoryPool(mMemoryPool); } -void ITSTrackingInterface::loadROF(gsl::span& trackROFspan, +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 69dddbf367653..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,49 +30,62 @@ 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 { LOGP(error, "Dropping this TF!"); - mTimeFrame->resetTracklets(); } }; 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) { @@ -86,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 153d7b6faa358..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,41 +10,48 @@ // or submit itself to any jurisdiction. /// +#include #include #include -#include -#include +#include +#include #include #include +#include #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()}; @@ -52,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; @@ -92,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]); } @@ -151,9 +154,16 @@ static void trackletSelectionKernelHost( offset12 += foundTracklets12[iCurrentLayerClusterIndex]; } } +} // namespace + +template +void VertexerTraits::initialise(const TrackingParameters& trackingParams) +{ + mTimeFrame->initialise(trackingParams, 3); +} -template -void VertexerTraits::updateVertexingParameters(const std::vector& vrtPar, const TimeFrameGPUParameters& tfPar) +template +void VertexerTraits::updateVertexingParameters(const std::vector& vrtPar) { mVrtParams = vrtPar; mIndexTableUtils.setTrackingParameters(vrtPar[0]); @@ -164,49 +174,51 @@ void VertexerTraits::updateVertexingParameters(const std::vector -void VertexerTraits::computeTracklets(const int iteration) +template +void VertexerTraits::computeTracklets(const int iteration) { mTaskArena->execute([&] { - tbb::parallel_for( - tbb::blocked_range(0, (short)mTimeFrame->getNrof()), - [&](const tbb::blocked_range& Rofs) { - for (short pivotRofId = Rofs.begin(); pivotRofId < Rofs.end(); ++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) { - 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 - 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, - gsl::span(), // Offset in the tracklet buffer - mVrtParams[iteration].maxTrackletsPerCluster); - trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), - !skipROF ? 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, - gsl::span(), // Offset in the tracklet buffer - mVrtParams[iteration].maxTrackletsPerCluster); - } - mTimeFrame->getNTrackletsROF(pivotRofId, 0) = std::accumulate(mTimeFrame->getNTrackletsCluster(pivotRofId, 0).begin(), mTimeFrame->getNTrackletsCluster(pivotRofId, 0).end(), 0); - mTimeFrame->getNTrackletsROF(pivotRofId, 1) = std::accumulate(mTimeFrame->getNTrackletsCluster(pivotRofId, 1).begin(), mTimeFrame->getNTrackletsCluster(pivotRofId, 1).end(), 0); - } - }); + 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( + !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, + 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( + !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, + 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); + mTimeFrame->getNTrackletsROF(pivotRofId, 1) = std::accumulate(mTimeFrame->getNTrackletsCluster(pivotRofId, 1).begin(), mTimeFrame->getNTrackletsCluster(pivotRofId, 1).end(), 0); + }); mTimeFrame->computeTrackletsPerROFScans(); if (auto tot0 = mTimeFrame->getTotalTrackletsTF(0), tot1 = mTimeFrame->getTotalTrackletsTF(1); @@ -217,629 +229,414 @@ void VertexerTraits::computeTracklets(const int iteration) mTimeFrame->getTracklets()[1].resize(tot1); } - tbb::parallel_for( - tbb::blocked_range(0, (short)mTimeFrame->getNrof()), - [&](const tbb::blocked_range& Rofs) { - for (short pivotRofId = Rofs.begin(); pivotRofId < Rofs.end(); ++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) { - trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 0) : gsl::span(), - !skipROF ? 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, - mTimeFrame->getExclusiveNTrackletsCluster(pivotRofId, 0), - mVrtParams[iteration].maxTrackletsPerCluster); - trackleterKernelHost( - !skipROF ? mTimeFrame->getClustersOnLayer(targetRofId, 2) : gsl::span(), - !skipROF ? 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, - mTimeFrame->getExclusiveNTrackletsCluster(pivotRofId, 1), - mVrtParams[iteration].maxTrackletsPerCluster); - } - } - }); + 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( + !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, + 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( + !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, + 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(); + } + const auto lhsBeam = clusterBeamDistance2(lhs); + const auto rhsBeam = clusterBeamDistance2(rhs); + if (o2::gpu::GPUCommonMath::Abs(lhsBeam - rhsBeam) > constants::Tolerance) { + return lhsBeam < rhsBeam; } - for (int line2{line1 + 1}; line2 < numTracklets; ++line2) { - if (usedTracklets[line2]) { + 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; - 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); - 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()), - }; - } - Vertex vert; - vert.setTimeStamp(rofId); - vert.setNContributors(std::ranges::count_if(mcReader.getTracks(iSrc, iEve), [](const auto& trk) { - return trk.isPrimary() && trk.GetPt() > 0.2 && std::abs(trk.GetEta()) < 1.3; - })); - vert.setXYZ((float)eve.GetX(), (float)eve.GetY(), (float)eve.GetZ()); - vert.setChi2(1); - 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); - } - } - } - 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); + 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); + auto bc = (ir - raw::HBFUtils::Instance().getFirstSampledTFIR()).toLong() - roFrameBiasInBC; + if (bc < 0) { // event happened before TF + continue; } - } else { - mTimeFrame->getNoVertexROF()++; + Vertex vert; + 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) { + 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 = 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); } - mTimeFrame->addPrimaryVertices(verts, 0); - mTimeFrame->addPrimaryVerticesLabels(polls); + mcReader.releaseTracksForSourceAndEvent(iSrc, iEve); } - 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) +template +bool VertexerTraits::skipROF(int iteration, int rof) const { - 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) -{ - 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 new file mode 100644 index 0000000000000..f8fce10b78602 --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/test/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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. + +o2_add_test(boundedmemoryresource + SOURCES testBoundedMemoryResource.cxx + 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/testBoundedMemoryResource.cxx b/Detectors/ITSMFT/ITS/tracking/test/testBoundedMemoryResource.cxx new file mode 100644 index 0000000000000..aae28f5cbc36e --- /dev/null +++ b/Detectors/ITSMFT/ITS/tracking/test/testBoundedMemoryResource.cxx @@ -0,0 +1,190 @@ +// 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 BOOST_TEST_MODULE Test Flags +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include +#include "ITStracking/BoundedAllocator.h" + +using namespace o2::its; +using Vec = bounded_vector; +auto getRandomInt(int min = -100, int max = 100) +{ + static std::mt19937 gen(std::random_device{}()); // static generator, seeded once + std::uniform_int_distribution<> dist(min, max); + return [&, dist]() mutable { + return dist(gen); + }; +} + +// -------- Throwing upstream resource for testing rollback -------- +class ThrowingResource final : public std::pmr::memory_resource +{ + protected: + void* do_allocate(size_t, size_t) final + { + throw std::bad_alloc(); // always fail + } + void do_deallocate(void*, size_t, size_t) noexcept final + { + // nothing + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final + { + return this == &other; + } +}; + +// -------- Upstream resource with empty deallocate -------- +class NoDeallocateResource final : public std::pmr::memory_resource +{ + public: + NoDeallocateResource(std::pmr::memory_resource* upstream = std::pmr::get_default_resource()) + : mUpstream(upstream) {} + + protected: + void* do_allocate(size_t bytes, size_t alignment) final + { + return mUpstream->allocate(bytes, alignment); + } + void do_deallocate(void*, size_t, size_t) noexcept final + { + // nothing + } + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final + { + return this == &other; + } + + private: + std::pmr::memory_resource* mUpstream; +}; + +// -------- Tests -------- +BOOST_AUTO_TEST_CASE(allocation_and_clear_updates_used_memory) +{ + BoundedMemoryResource bmr(10 * 1024 * 1024); // 10 MB cap + + Vec v(std::pmr::polymorphic_allocator{&bmr}); + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0u); + + const size_t count = 128; + v.reserve(count); + const size_t expected = count * sizeof(int); + BOOST_CHECK_GE(bmr.getUsedMemory(), expected); + BOOST_CHECK_LE(bmr.getUsedMemory(), expected + 64); + + deepVectorClear(v, &bmr); + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0u); +} + +BOOST_AUTO_TEST_CASE(clearResizeBoundedVector_resizes_and_tracks_memory) +{ + BoundedMemoryResource bmr(1024 * 1024); // 1 MB cap + + Vec v(std::pmr::polymorphic_allocator{&bmr}); + v.reserve(200); + const size_t used_before = bmr.getUsedMemory(); + BOOST_CHECK_GT(used_before, 0u); + + clearResizeBoundedVector(v, 50, &bmr, 7); + const size_t used_after = bmr.getUsedMemory(); + BOOST_CHECK_GE(used_after, 50 * sizeof(int)); + BOOST_CHECK_LT(used_after, used_before); + + clearResizeBoundedVector(v, 300, &bmr, 3); + BOOST_CHECK_GE(bmr.getUsedMemory(), 300 * sizeof(int)); +} + +BOOST_AUTO_TEST_CASE(upstream_throw_rolls_back_reservation) +{ + ThrowingResource upstream; + BoundedMemoryResource bmr(std::numeric_limits::max(), &upstream); + const size_t bytes = 1024; + bool threw = false; + void* p{nullptr}; + try { + p = bmr.allocate(bytes, alignof(std::max_align_t)); + } catch (const std::bad_alloc&) { + threw = true; + } + BOOST_CHECK(threw); + BOOST_CHECK_EQUAL(p, nullptr); + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0u); +} + +BOOST_AUTO_TEST_CASE(vector_of_bounded_vectors_deep_clear_releases_all) +{ + BoundedMemoryResource bmr(10 * 1024 * 1024); // 10 MB + std::vector outer; + outer.reserve(5); + for (int i = 0; i < 5; ++i) { + outer.emplace_back(std::pmr::polymorphic_allocator{&bmr}); + outer.back().reserve(100); + } + BOOST_CHECK_GT(bmr.getUsedMemory(), 0u); + deepVectorClear(outer, &bmr); // deep clear outer + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0u); +} + +BOOST_AUTO_TEST_CASE(array_of_bounded_vectors_clear_resize_works) +{ + BoundedMemoryResource bmr(10 * 1024 * 1024); + std::array arr{{Vec(std::pmr::polymorphic_allocator{&bmr}), + Vec(std::pmr::polymorphic_allocator{&bmr}), + Vec(std::pmr::polymorphic_allocator{&bmr})}}; + clearResizeBoundedVector(arr[0], 10, &bmr, 1); + clearResizeBoundedVector(arr[1], 20, &bmr, 2); + clearResizeBoundedVector(arr[2], 30, &bmr, 3); + BOOST_CHECK_GT(bmr.getUsedMemory(), 0u); + deepVectorClear(arr, &bmr); // now clear all recursively + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0u); +} + +BOOST_AUTO_TEST_CASE(deepVectorClear_releases_and_reuses_resource) +{ + // Use a small bounded memory resource + BoundedMemoryResource bmr(1024); + bounded_vector vec{std::pmr::polymorphic_allocator{&bmr}}; + vec.resize(100, 42); + BOOST_TEST(bmr.getUsedMemory() > 0); + deepVectorClear(vec, &bmr); + BOOST_TEST(vec.empty()); + BOOST_TEST(vec.get_allocator().resource() == &bmr); + auto usedAfter = bmr.getUsedMemory(); + BOOST_CHECK_EQUAL(bmr.getUsedMemory(), 0); + vec.push_back(7); + BOOST_TEST(vec.size() == 1); + BOOST_TEST(vec[0] == 7); + BOOST_TEST(vec.get_allocator().resource() == &bmr); +} + +BOOST_AUTO_TEST_CASE(clear_with_memory_resource_without_deallocator) +{ + NoDeallocateResource dmr; + Vec v(std::pmr::polymorphic_allocator{&dmr}); + + for (int shift{0}; shift < 12; ++shift) { + const int c{1 << shift}; + v.resize(100); + std::generate(v.begin(), v.end(), getRandomInt()); + // allocate different sizes, which is actually a no-op now + clearResizeBoundedVector(v, c / 2, &dmr, 999); + for (size_t i{0}; i < c / 2; ++i) { // now only the first c/2 elements should be set + BOOST_CHECK_EQUAL(v[i], 999); + } + // try to deepclear + deepVectorClear(v); + } +} 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/CMakeLists.txt b/Detectors/ITSMFT/ITS/workflow/CMakeLists.txt index 3609560eccf72..10e16e49d92b5 100644 --- a/Detectors/ITSMFT/ITS/workflow/CMakeLists.txt +++ b/Detectors/ITSMFT/ITS/workflow/CMakeLists.txt @@ -13,10 +13,7 @@ o2_add_library(ITSWorkflow TARGETVARNAME targetName SOURCES src/RecoWorkflow.cxx src/ClusterWriterWorkflow.cxx - src/ClustererSpec.cxx - src/ClusterWriterSpec.cxx src/TrackerSpec.cxx - src/CookedTrackerSpec.cxx src/TrackWriterSpec.cxx src/TrackReaderSpec.cxx src/VertexReaderSpec.cxx 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/ClustererSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClustererSpec.h deleted file mode 100644 index c5038c87fa467..0000000000000 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClustererSpec.h +++ /dev/null @@ -1,65 +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 ClustererSpec.h - -#ifndef O2_ITS_CLUSTERERDPL -#define O2_ITS_CLUSTERERDPL - -#include -#include "DetectorsBase/GRPGeomHelper.h" -#include "ITSMFTReconstruction/Clusterer.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/Task.h" - -using namespace o2::framework; - -namespace o2 -{ - -namespace itsmft -{ -class Clusterer; -} - -namespace its -{ - -class ClustererDPL : public Task -{ - public: - ClustererDPL(std::shared_ptr gr, bool useMC) : mGGCCDBRequest(gr), mUseMC(useMC) {} - ~ClustererDPL() override = default; - void init(InitContext& ic) final; - void run(ProcessingContext& pc) final; - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; - void endOfStream(o2::framework::EndOfStreamContext& ec) final; - - private: - void updateTimeDependentParams(ProcessingContext& pc); - - int mState = 0; - bool mUseMC = true; - bool mUseClusterDictionary = true; - int mNThreads = 1; - std::unique_ptr mFile = nullptr; - std::unique_ptr mClusterer = nullptr; - std::shared_ptr mGGCCDBRequest; -}; - -/// create a processor spec -/// run ITS cluster finder -framework::DataProcessorSpec getClustererSpec(bool useMC); - -} // namespace its -} // namespace o2 - -#endif /* O2_ITS_CLUSTERERDPL */ diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/CookedTrackerSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/CookedTrackerSpec.h deleted file mode 100644 index 4ecc98eed9cfb..0000000000000 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/CookedTrackerSpec.h +++ /dev/null @@ -1,75 +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 CookedTrackerSpec.h - -#ifndef O2_ITS_COOKEDTRACKERDPL -#define O2_ITS_COOKEDTRACKERDPL - -#include "Framework/DataProcessorSpec.h" -#include "ITSReconstruction/CookedTracker.h" -#include "ITStracking/TimeFrame.h" -#include "ITStracking/Vertexer.h" -#include "ITStracking/VertexerTraits.h" -#include "ITStracking/BoundedAllocator.h" -#include "DataFormatsParameters/GRPObject.h" -#include "DataFormatsITSMFT/TopologyDictionary.h" -#include "Framework/Task.h" -#include "TStopwatch.h" -#include "DetectorsBase/GRPGeomHelper.h" - -#include - -using namespace o2::framework; - -namespace o2 -{ -namespace its -{ - -class CookedTrackerDPL : public Task -{ - public: - CookedTrackerDPL(std::shared_ptr gr, bool useMC, int trgType, TrackingMode::Type trMode); - ~CookedTrackerDPL() override = default; - void init(InitContext& ic) final; - void run(ProcessingContext& pc) final; - void endOfStream(framework::EndOfStreamContext& ec) final; - void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; - void setClusterDictionary(const o2::itsmft::TopologyDictionary* d) { mDict = d; } - - private: - void updateTimeDependentParams(ProcessingContext& pc); - - std::shared_ptr mGGCCDBRequest; - int mState = 0; - bool mUseMC = true; - bool mRunVertexer = true; - int mUseTriggers = 0; - TrackingMode::Type mMode = TrackingMode::Sync; - const o2::itsmft::TopologyDictionary* mDict = nullptr; - std::unique_ptr mGRP = nullptr; - o2::its::CookedTracker mTracker; - std::unique_ptr> mVertexerTraitsPtr = nullptr; - std::unique_ptr> mVertexerPtr = nullptr; - std::shared_ptr mMemoryPool; - std::shared_ptr mTaskArena; - TStopwatch mTimer; -}; - -/// create a processor spec -/// run ITS CookedMatrix tracker -framework::DataProcessorSpec getCookedTrackerSpec(bool useMC, bool useGeom, int useTrig, TrackingMode::Type trMode); - -} // namespace its -} // namespace o2 - -#endif /* O2_ITS_COOKEDTRACKERDPL */ 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 0ab48d713f7c7..3068954c92003 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/RecoWorkflow.h @@ -16,7 +16,7 @@ #include "Framework/WorkflowSpec.h" #include "ITStracking/Configuration.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesConfig.h" namespace o2 { @@ -26,9 +26,9 @@ namespace its namespace reco_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, bool useCAtracker, TrackingMode::Type trmode, const bool overrideBeamPosition = false, - bool upstreamDigits = false, bool upstreamClusters = false, bool disableRootOutput = false, bool useGeom = false, int useTrig = 0, - bool useGPUWF = false, o2::gpu::GPUDataTypes::DeviceType dType = o2::gpu::GPUDataTypes::DeviceType::CPU); +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); } } // namespace its diff --git a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ThresholdCalibratorSpec.h b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ThresholdCalibratorSpec.h index 2a139f7997dfb..a768b848c7095 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ThresholdCalibratorSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ThresholdCalibratorSpec.h @@ -175,6 +175,7 @@ class ITSThresholdCalibrator : public Task unsigned char vCharge[N_COL]; unsigned char vHits[N_COL]; short int mColStep = 8; // save s-curves to tree every mColStep pixels on 1 row + short int mRowStep = 1; // Initialize pointers for doing error function fits TH1F* mFitHist = nullptr; @@ -232,7 +233,8 @@ class ITSThresholdCalibrator : public Task short int mRunTypeUp = -1; short int mRunTypeRU[N_RU] = {0}; short int mRunTypeRUCopy[N_RU] = {0}; - short int mCdwCntRU[N_RU][N_ROW] = {{0}}; + bool mFlagsRU[N_RU] = {0}; + std::map, 500>>> mCdwCntRU; // RU --> row --> 2D hit map short int mLoopVal[N_RU][N_ROW] = {{0}}; bool mActiveLinks[N_RU][3] = {{false}}; std::set mRuSet; @@ -241,6 +243,7 @@ class ITSThresholdCalibrator : public Task short int mMin = -1, mMax = -1, mMin2 = 0, mMax2 = 0; short int mStep = 1, mStep2 = 1; short int mStrobeWindow = 5; // 5 means 5*25ns = 125 ns + short int mRowScan = 512; // number of scanned rows, used only to normalize % of success // Get threshold method (fit == 1, derivative == 0, or hitcounting == 2) char mFitType = -1; @@ -293,6 +296,7 @@ class ITSThresholdCalibrator : public Task short int manualStep = 1, manualStep2 = 1; std::string manualScanType; short int manualStrobeWindow = 5; + short int manualRowScan = 512; // used only to normalize % of success in thr/ithr/vcasn scans // for CRU_ITS data processing bool isCRUITS = false; @@ -306,7 +310,7 @@ class ITSThresholdCalibrator : public Task int maxDumpS = -1; // maximum number of s-curves to be dumped, default -1 = dump all std::string chipDumpS = ""; // list of comma-separated O2 chipIDs to be dumped, default is empty = dump all int dumpCounterS[24120] = {0}; // count dumps for every chip - int countCdw[24120] = {0}; // count how many CDWs have been processed with the maximum charge injected: usefull for s-curve dump when hits do not arrive in order + bool isChipDB[24120] = {0}; // check whether a chip has been already added to DB entry TFile* fileDumpS; // file where to store the s-curves on disk std::vector chipDumpList; // vector of chips to dump @@ -324,6 +328,9 @@ class ITSThresholdCalibrator : public Task // Percentage cut for VCASN/ITHR scans short int mPercentageCut = 25; // default, at least 1 good row equivalent + + // For data replay only + short int isLocal = false; }; // Create a processor spec 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 ee5ba4d5cc61c..8ce63efcb7a3b 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h +++ b/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/TrackerSpec.h @@ -23,11 +23,17 @@ #include "ITStracking/TrackingInterface.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesConfig.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TStopwatch.h" +namespace o2::gpu +{ +class GPUReconstruction; +class GPUChainITS; +} // namespace o2::gpu + namespace o2::its { @@ -36,10 +42,11 @@ 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, - o2::gpu::GPUDataTypes::DeviceType dType = o2::gpu::GPUDataTypes::DeviceType::CPU); + o2::gpu::gpudatatypes::DeviceType dType = o2::gpu::gpudatatypes::DeviceType::CPU); ~TrackerDPL() override = default; void init(framework::InitContext& ic) final; void run(framework::ProcessingContext& pc) final; @@ -57,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/ClusterWriterSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterSpec.cxx deleted file mode 100644 index 4dffbaf88893c..0000000000000 --- a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterSpec.cxx +++ /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 ClusterWriterSpec.cxx - -#include - -#include "ITSWorkflow/ClusterWriterSpec.h" -#include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace its -{ - -template -using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; -using CompClusType = std::vector; -using PatternsType = std::vector; -using ROFrameRType = std::vector; -using LabelsType = o2::dataformats::MCTruthContainer; -using ROFRecLblT = std::vector; -using namespace o2::header; - -DataProcessorSpec getClusterWriterSpec(bool useMC) -{ - // Spectators for logging - // this is only to restore the original behavior - auto compClustersSize = std::make_shared(0); - auto compClustersSizeGetter = [compClustersSize](CompClusType const& compClusters) { - *compClustersSize = compClusters.size(); - }; - auto logger = [compClustersSize](std::vector const& rofs) { - LOG(info) << "ITSClusterWriter pulled " << *compClustersSize << " clusters, in " << rofs.size() << " RO frames"; - }; - return MakeRootTreeWriterSpec("its-cluster-writer", - "o2clus_its.root", - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Tree with ITS clusters"}, - BranchDefinition{InputSpec{"compclus", "ITS", "COMPCLUSTERS", 0}, - "ITSClusterComp", - compClustersSizeGetter}, - BranchDefinition{InputSpec{"patterns", "ITS", "PATTERNS", 0}, - "ITSClusterPatt"}, - BranchDefinition{InputSpec{"ROframes", "ITS", "CLUSTERSROF", 0}, - "ITSClustersROF", - logger}, - BranchDefinition{InputSpec{"labels", "ITS", "CLUSTERSMCTR", 0}, - "ITSClusterMCTruth", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""}, - BranchDefinition{InputSpec{"MC2ROframes", "ITS", "CLUSTERSMC2ROF", 0}, - "ITSClustersMC2ROF", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""})(); -} - -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx index ca5db7acd63e1..e05e55ffabd18 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/ClusterWriterWorkflow.cxx @@ -12,7 +12,7 @@ /// @file ClusterWriterWorkflow.cxx #include "ITSWorkflow/ClusterWriterWorkflow.h" -#include "ITSWorkflow/ClusterWriterSpec.h" +#include "ITSMFTWorkflow/ClusterWriterSpec.h" namespace o2 { @@ -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::its::getClusterWriterSpec(useMC)); + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clusterROFOnly)); return specs; } diff --git a/Detectors/ITSMFT/ITS/workflow/src/ClustererSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/ClustererSpec.cxx deleted file mode 100644 index d58e4f5d915c1..0000000000000 --- a/Detectors/ITSMFT/ITS/workflow/src/ClustererSpec.cxx +++ /dev/null @@ -1,218 +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 ClustererSpec.cxx - -#include - -#include "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "ITSWorkflow/ClustererSpec.h" -#include "DataFormatsITSMFT/Digit.h" -#include "ITSMFTReconstruction/ChipMappingITS.h" -#include "ITSMFTReconstruction/ClustererParam.h" -#include "DataFormatsITSMFT/TopologyDictionary.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/ConstMCTruthContainer.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "DataFormatsParameters/GRPObject.h" -#include "ITSMFTReconstruction/DigitPixelReader.h" -#include "ITSMFTBase/DPLAlpideParam.h" -#include "CommonConstants/LHCConstants.h" -#include "DetectorsCommonDataFormats/DetectorNameConf.h" - -using namespace o2::framework; -using namespace o2::itsmft; - -namespace o2 -{ -namespace its -{ - -void ClustererDPL::init(InitContext& ic) -{ - mClusterer = std::make_unique(); - mClusterer->setNChips(o2::itsmft::ChipMappingITS::getNChips()); - mUseClusterDictionary = !ic.options().get("ignore-cluster-dictionary"); - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - mNThreads = std::max(1, ic.options().get("nthreads")); - LOGP(info, "Initialising ITSClusterer with {} threads", mNThreads); - mState = 1; -} - -void ClustererDPL::run(ProcessingContext& pc) -{ - updateTimeDependentParams(pc); - auto digits = pc.inputs().get>("digits"); - auto rofs = pc.inputs().get>("ROframes"); - - gsl::span mc2rofs; - gsl::span labelbuffer; - if (mUseMC) { - labelbuffer = pc.inputs().get>("labels"); - mc2rofs = pc.inputs().get>("MC2ROframes"); - } - o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - - LOG(info) << "ITSClusterer pulled " << digits.size() << " digits, in " - << rofs.size() << " RO frames"; - LOG(info) << "ITSClusterer pulled " << labels.getNElements() << " labels "; - - o2::itsmft::DigitPixelReader reader; - reader.setSquashingDepth(mClusterer->getMaxROFDepthToSquash()); - reader.setSquashingDist(mClusterer->getMaxRowColDiffToMask()); // Sharing same parameter/logic with masking - reader.setMaxBCSeparationToSquash(mClusterer->getMaxBCSeparationToSquash()); - reader.setDigits(digits); - reader.setROFRecords(rofs); - if (mUseMC) { - reader.setMC2ROFRecords(mc2rofs); - reader.setDigitsMCTruth(labels.getIndexedSize() > 0 ? &labels : nullptr); - } - reader.init(); - auto orig = o2::header::gDataOriginITS; - std::vector clusCompVec; - std::vector clusROFVec; - std::vector clusPattVec; - - std::unique_ptr> clusterLabels; - if (mUseMC) { - clusterLabels = std::make_unique>(); - } - mClusterer->process(mNThreads, reader, &clusCompVec, &clusPattVec, &clusROFVec, clusterLabels.get()); - pc.outputs().snapshot(Output{orig, "COMPCLUSTERS", 0}, clusCompVec); - pc.outputs().snapshot(Output{orig, "CLUSTERSROF", 0}, clusROFVec); - pc.outputs().snapshot(Output{orig, "PATTERNS", 0}, clusPattVec); - - if (mUseMC) { - pc.outputs().snapshot(Output{orig, "CLUSTERSMCTR", 0}, *clusterLabels.get()); // at the moment requires snapshot - std::vector clusterMC2ROframes(mc2rofs.size()); - for (int i = mc2rofs.size(); i--;) { - clusterMC2ROframes[i] = mc2rofs[i]; // Simply, replicate it from digits ? - } - pc.outputs().snapshot(Output{orig, "CLUSTERSMC2ROF", 0}, clusterMC2ROframes); - } - - // TODO: in principle, after masking "overflow" pixels the MC2ROFRecord maxROF supposed to change, nominally to minROF - // -> consider recalculationg maxROF - LOG(info) << "ITSClusterer pushed " << clusCompVec.size() << " clusters, in " << clusROFVec.size() << " RO frames"; -} - -///_______________________________________ -void ClustererDPL::updateTimeDependentParams(ProcessingContext& pc) -{ - static bool initOnceDone = false; - o2::base::GRPGeomHelper::instance().checkUpdates(pc); - if (!initOnceDone) { // this params need to be queried only once - initOnceDone = true; - pc.inputs().get("cldict"); // just to trigger the finaliseCCDB - pc.inputs().get*>("alppar"); - pc.inputs().get*>("cluspar"); - mClusterer->setContinuousReadOut(o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(o2::detectors::DetID::ITS)); - // settings for the fired pixel overflow masking - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - const auto& clParams = o2::itsmft::ClustererParam::Instance(); - mClusterer->setDropHugeClusters(clParams.dropHugeClusters); - if (clParams.maxBCDiffToMaskBias > 0 && clParams.maxBCDiffToSquashBias > 0) { - LOGP(fatal, "maxBCDiffToMaskBias = {} and maxBCDiffToSquashBias = {} cannot be set at the same time. Either set masking or squashing with a BCDiff > 0", clParams.maxBCDiffToMaskBias, clParams.maxBCDiffToSquashBias); - } - auto nbc = clParams.maxBCDiffToMaskBias; - nbc += mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); - mClusterer->setMaxBCSeparationToMask(nbc); - mClusterer->setMaxRowColDiffToMask(clParams.maxRowColDiffToMask); - // Squasher - int rofBC = mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); // ROF length in BC - mClusterer->setMaxBCSeparationToSquash(rofBC + clParams.maxBCDiffToSquashBias); - int nROFsToSquash = 0; // squashing disabled if no reset due to maxSOTMUS>0. - if (clParams.maxSOTMUS > 0 && rofBC > 0) { - nROFsToSquash = 2 + int(clParams.maxSOTMUS / (rofBC * o2::constants::lhc::LHCBunchSpacingMUS)); // use squashing - } - mClusterer->setMaxROFDepthToSquash(clParams.maxBCDiffToSquashBias > 0 ? nROFsToSquash : 0); - mClusterer->print(); - } - // we may have other params which need to be queried regularly -} - -///_______________________________________ -void ClustererDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -{ - if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { - return; - } - if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { - LOG(info) << "cluster dictionary updated" << (!mUseClusterDictionary ? " but its using is disabled" : ""); - if (mUseClusterDictionary) { - mClusterer->setDictionary((const o2::itsmft::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("ITS", "ALPIDEPARAM", 0)) { - LOG(info) << "Alpide param updated"; - const auto& par = o2::itsmft::DPLAlpideParam::Instance(); - par.printKeyValues(); - return; - } - if (matcher == ConcreteDataMatcher("ITS", "CLUSPARAM", 0)) { - LOG(info) << "Cluster param updated"; - const auto& par = o2::itsmft::ClustererParam::Instance(); - par.printKeyValues(); - return; - } -} - -DataProcessorSpec getClustererSpec(bool useMC) -{ - std::vector inputs; - inputs.emplace_back("digits", "ITS", "DIGITS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", "ITS", "DIGITSROF", 0, Lifetime::Timeframe); - inputs.emplace_back("cldict", "ITS", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("ITS/Calib/ClusterDictionary")); - inputs.emplace_back("cluspar", "ITS", "CLUSPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/ClustererParam")); - inputs.emplace_back("alppar", "ITS", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/AlpideParam")); - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - false, // GRPLHCIF - false, // GRPMagField - false, // askMatLUT - o2::base::GRPGeomRequest::None, // geometry - inputs, - true); - std::vector outputs; - outputs.emplace_back("ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "PATTERNS", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "CLUSTERSROF", 0, Lifetime::Timeframe); - - if (useMC) { - inputs.emplace_back("labels", "ITS", "DIGITSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "ITS", "DIGITSMC2ROF", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); - } - - return DataProcessorSpec{ - "its-clusterer", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, useMC)}, - Options{ - {"ignore-cluster-dictionary", VariantType::Bool, false, {"do not use cluster dictionary, always store explicit patterns"}}, - {"nthreads", VariantType::Int, 1, {"Number of clustering threads"}}}}; -} - -///_______________________________________ -void ClustererDPL::endOfStream(o2::framework::EndOfStreamContext& ec) -{ - mClusterer->print(); -} - -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/workflow/src/CookedTrackerSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/CookedTrackerSpec.cxx deleted file mode 100644 index b989a78e59b7c..0000000000000 --- a/Detectors/ITSMFT/ITS/workflow/src/CookedTrackerSpec.cxx +++ /dev/null @@ -1,327 +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 CookedTrackerSpec.cxx - -#include - -#include "TGeoGlobalMagField.h" - -#include "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "ITSWorkflow/CookedTrackerSpec.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/Cluster.h" -#include "DataFormatsITS/TrackITS.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include -#include "ITSMFTBase/DPLAlpideParam.h" -#include "DataFormatsTRD/TriggerRecord.h" - -#include "Field/MagneticField.h" -#include "DetectorsBase/GeometryManager.h" -#include "DetectorsBase/Propagator.h" -#include "ITSBase/GeometryTGeo.h" -#include "CommonDataFormat/IRFrame.h" -#include "ITStracking/IOUtils.h" -#include "DetectorsCommonDataFormats/DetectorNameConf.h" -#include "CommonUtils/StringUtils.h" - -#include "ITSReconstruction/FastMultEstConfig.h" -#include "ITSReconstruction/FastMultEst.h" -#include "ITSMFTReconstruction/ClustererParam.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace its -{ - -using Vertex = o2::dataformats::Vertex>; - -CookedTrackerDPL::CookedTrackerDPL(std::shared_ptr gr, bool useMC, int trgType, TrackingMode::Type trMode) : mGGCCDBRequest(gr), mUseMC(useMC), mUseTriggers{trgType}, mMode(trMode) -{ - mVertexerTraitsPtr = std::make_unique>(); - mVertexerPtr = std::make_unique>(mVertexerTraitsPtr.get()); -} - -void CookedTrackerDPL::init(InitContext& ic) -{ - mTimer.Stop(); - mTimer.Reset(); - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - auto nthreads = ic.options().get("nthreads"); - mTracker.setNumberOfThreads(nthreads); - mTaskArena = std::make_shared(nthreads); - mMemoryPool = std::make_unique(); - mVertexerPtr->setMemoryPool(mMemoryPool); - mVertexerPtr->setNThreads(nthreads, mTaskArena); - mVertexerTraitsPtr->setMemoryPool(mMemoryPool); -} - -void CookedTrackerDPL::run(ProcessingContext& pc) -{ - mTimer.Start(false); - updateTimeDependentParams(pc); - auto compClusters = pc.inputs().get>("compClusters"); - gsl::span patterns = pc.inputs().get>("patterns"); - - // code further down does assignment to the rofs and the altered object is used for output - // we therefore need a copy of the vector rather than an object created directly on the input data, - // the output vector however is created directly inside the message memory thus avoiding copy by - // snapshot - auto rofsinput = pc.inputs().get>("ROframes"); - gsl::span physTriggers; - std::vector fromTRD; - if (mUseTriggers == 2) { // use TRD triggers - o2::InteractionRecord ir{0, pc.services().get().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}); - } - } - physTriggers = gsl::span(fromTRD.data(), fromTRD.size()); - } else if (mUseTriggers == 1) { // use Phys triggers from ITS stream - physTriggers = pc.inputs().get>("phystrig"); - } - - auto& rofs = pc.outputs().make>(Output{"ITS", "ITSTrackROF", 0}, rofsinput.begin(), rofsinput.end()); - - std::unique_ptr> labels; - gsl::span mc2rofs; - if (mUseMC) { - labels = pc.inputs().get*>("labels"); - // get the array as read-onlt span, a snapshot is send forward - mc2rofs = pc.inputs().get>("MC2ROframes"); - } - TimeFrame mTimeFrame; - mTimeFrame.setMemoryPool(mMemoryPool); - - LOG(info) << "ITSCookedTracker pulled " << compClusters.size() << " clusters, in " << rofs.size() << " RO frames"; - - std::vector trackLabels; - if (mUseMC) { - mTracker.setMCTruthContainers(labels.get(), &trackLabels); - } - - mVertexerPtr->adoptTimeFrame(mTimeFrame); - - auto& vertROFvec = pc.outputs().make>(Output{"ITS", "VERTICESROF", 0}); - auto& vertices = pc.outputs().make>(Output{"ITS", "VERTICES", 0}); - auto& tracks = pc.outputs().make>(Output{"ITS", "TRACKS", 0}); - auto& clusIdx = pc.outputs().make>(Output{"ITS", "TRACKCLSID", 0}); - auto& irFrames = pc.outputs().make>(Output{"ITS", "IRFRAMES", 0}); - - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); // RS: this should come from CCDB - int nBCPerTF = mTracker.getContinuousMode() ? alpParams.roFrameLengthInBC : alpParams.roFrameLengthTrig; - - gsl::span::iterator pattIt_timeframe = patterns.begin(); - gsl::span::iterator pattIt_tracker = patterns.begin(); - gsl::span rofspan(rofs); - mTimeFrame.loadROFrameData(rofspan, compClusters, pattIt_timeframe, mDict, labels.get()); - - const auto& multEstConf = FastMultEstConfig::Instance(); // parameters for mult estimation and cuts - FastMultEst multEst; // mult estimator - std::vector processingMask; - int cutVertexMult{0}, cutRandomMult = int(rofsinput.size()) - multEst.selectROFs(rofsinput, compClusters, physTriggers, processingMask); - - // auto processingMask_ephemeral = processingMask; - mTimeFrame.setMultiplicityCutMask(processingMask); - float vertexerElapsedTime; - if (mRunVertexer) { - vertexerElapsedTime = mVertexerPtr->clustersToVertices([&](std::string s) { LOG(info) << s; }); - } - LOG(info) << fmt::format(" - Vertex seeding total elapsed time: {} ms in {} ROFs", vertexerElapsedTime, rofspan.size()); - for (size_t iRof{0}; iRof < rofspan.size(); ++iRof) { - auto& rof = rofspan[iRof]; - - auto& vtxROF = vertROFvec.emplace_back(rof); // register entry and number of vertices in the - vtxROF.setFirstEntry(vertices.size()); - vtxROF.setNEntries(0); - if (!processingMask[iRof]) { - rof.setFirstEntry(tracks.size()); - rof.setNEntries(0); - continue; - } - - std::vector>> vtxVecLoc; - for (auto& v : mTimeFrame.getPrimaryVertices(iRof)) { - vtxVecLoc.push_back(v); - } - - if (multEstConf.isVtxMultCutRequested()) { // cut was requested - std::vector>> vtxVecSel; - vtxVecSel.swap(vtxVecLoc); - int nv = vtxVecSel.size(), nrej = 0; - for (const auto& vtx : vtxVecSel) { - if (!multEstConf.isPassingVtxMultCut(vtx.getNContributors())) { - LOG(info) << "Found vertex mult. " << vtx.getNContributors() << " is outside of requested range " << multEstConf.cutMultVtxLow << " : " << multEstConf.cutMultVtxHigh << " | ROF " << rof.getBCData(); - nrej++; - continue; // skip vertex of unwanted multiplicity - } - vtxVecLoc.push_back(vtx); - } - if (nv && (nrej == nv)) { // all vertices were rejected - cutVertexMult++; - processingMask[iRof] = false; - } - } - if (vtxVecLoc.empty()) { - if (multEstConf.cutMultVtxLow < 1) { // do blind search only if there is no cut on the low mult vertices - vtxVecLoc.emplace_back(); - } else { - rof.setFirstEntry(tracks.size()); - rof.setNEntries(0); - continue; - } - } else { // save vertices - vtxROF.setNEntries(vtxVecLoc.size()); - for (const auto& vtx : vtxVecLoc) { - vertices.push_back(vtx); - } - } - - mTracker.setVertices(vtxVecLoc); - mTracker.process(compClusters, pattIt_tracker, mDict, tracks, clusIdx, rof); - if (processingMask[iRof]) { - irFrames.emplace_back(rof.getBCData(), rof.getBCData() + nBCPerTF - 1).info = tracks.size(); - } - } - LOGP(info, " - rejected {}/{} ROFs: random/mult.sel:{} (seed {}), vtx.sel:{}", cutRandomMult + cutVertexMult, rofspan.size(), cutRandomMult, multEst.lastRandomSeed, cutVertexMult); - LOG(info) << "ITSCookedTracker pushed " << tracks.size() << " tracks and " << vertices.size() << " vertices"; - - if (mUseMC) { - pc.outputs().snapshot(Output{"ITS", "TRACKSMCTR", 0}, trackLabels); - pc.outputs().snapshot(Output{"ITS", "ITSTrackMC2ROF", 0}, mc2rofs); - } - mTimer.Stop(); -} - -void CookedTrackerDPL::endOfStream(EndOfStreamContext& ec) -{ - LOGF(info, "ITS Cooked-Tracker total timing: Cpu: %.3e Real: %.3e s in %d slots", - mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); -} - -///_______________________________________ -void CookedTrackerDPL::updateTimeDependentParams(ProcessingContext& pc) -{ - o2::base::GRPGeomHelper::instance().checkUpdates(pc); - static bool initOnceDone = false; - if (!initOnceDone) { // this params need to be queried only once - initOnceDone = true; - pc.inputs().get("cldict"); // just to trigger the finaliseCCDB - pc.inputs().get*>("alppar"); - if (pc.inputs().getPos("itsTGeo") >= 0) { - pc.inputs().get("itsTGeo"); - } - mVertexerPtr->setParameters(TrackingMode::getVertexingParameters(mMode)); - o2::its::GeometryTGeo* geom = o2::its::GeometryTGeo::Instance(); - geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::T2GRot, - o2::math_utils::TransformType::T2G)); - mTracker.setGeometry(geom); - mTracker.setConfigParams(); - LOG(info) << "Tracking mode " << TrackingMode::toString(mMode); - if (mMode == TrackingMode::Cosmics) { - LOG(info) << "Setting cosmics parameters..."; - mTracker.setParametersCosmics(); - mRunVertexer = false; - } - mTracker.setBz(o2::base::Propagator::Instance()->getNominalBz()); - bool continuous = o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(o2::detectors::DetID::ITS); - LOG(info) << "ITSCookedTracker RO: continuous=" << continuous; - mTracker.setContinuousMode(continuous); - } -} - -///_______________________________________ -void CookedTrackerDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -{ - if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { - return; - } - if (matcher == ConcreteDataMatcher("ITS", "CLUSDICT", 0)) { - LOG(info) << "cluster dictionary updated"; - setClusterDictionary((const o2::itsmft::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("ITS", "ALPIDEPARAM", 0)) { - LOG(info) << "Alpide param updated"; - const auto& par = o2::itsmft::DPLAlpideParam::Instance(); - par.printKeyValues(); - return; - } - if (matcher == ConcreteDataMatcher("ITS", "GEOMTGEO", 0)) { - LOG(info) << "ITS GeometryTGeo loaded from ccdb"; - o2::its::GeometryTGeo::adopt((o2::its::GeometryTGeo*)obj); - return; - } -} - -DataProcessorSpec getCookedTrackerSpec(bool useMC, bool useGeom, int trgType, TrackingMode::Type trmode) -{ - 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); - inputs.emplace_back("cldict", "ITS", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("ITS/Calib/ClusterDictionary")); - inputs.emplace_back("alppar", "ITS", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("ITS/Config/AlpideParam")); - if (trgType == 1) { - inputs.emplace_back("phystrig", "ITS", "PHYSTRIG", 0, Lifetime::Timeframe); - } else if (trgType == 2) { - inputs.emplace_back("phystrig", "TRD", "TRKTRGRD", 0, Lifetime::Timeframe); - } - - std::vector outputs; - outputs.emplace_back("ITS", "TRACKS", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "TRACKCLSID", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "ITSTrackROF", 0, Lifetime::Timeframe); - 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("labels", "ITS", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "ITS", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "TRACKSMCTR", 0, Lifetime::Timeframe); - outputs.emplace_back("ITS", "ITSTrackMC2ROF", 0, Lifetime::Timeframe); - } - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - false, // GRPLHCIF - true, // GRPMagField - true, // askMatLUT - useGeom ? o2::base::GRPGeomRequest::Aligned : o2::base::GRPGeomRequest::None, // geometry - inputs, - true); - if (!useGeom) { - ggRequest->addInput({"itsTGeo", "ITS", "GEOMTGEO", 0, Lifetime::Condition, framework::ccdbParamSpec("ITS/Config/Geometry")}, inputs); - } - return DataProcessorSpec{ - "its-cooked-tracker", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, - useMC, - trgType, - trmode)}, - Options{{"nthreads", VariantType::Int, 1, {"Number of threads"}}}}; -} - -} // namespace its -} // namespace o2 diff --git a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx index 948d2c1b53009..06b3f019a6be7 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/RecoWorkflow.cxx @@ -12,10 +12,9 @@ /// @file RecoWorkflow.cxx #include "ITSWorkflow/RecoWorkflow.h" -#include "ITSWorkflow/ClustererSpec.h" -#include "ITSWorkflow/ClusterWriterSpec.h" +#include "ITSMFTWorkflow/ClustererSpec.h" +#include "ITSMFTWorkflow/ClusterWriterSpec.h" #include "ITSWorkflow/TrackerSpec.h" -#include "ITSWorkflow/CookedTrackerSpec.h" #include "ITSWorkflow/TrackWriterSpec.h" #include "ITStracking/TrackingConfigParam.h" #include "ITSMFTWorkflow/DigitReaderSpec.h" @@ -28,63 +27,60 @@ namespace o2::its::reco_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, - bool useCAtracker, +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, bool useGPUWF, - o2::gpu::GPUDataTypes::DeviceType dtype) + o2::gpu::gpudatatypes::DeviceType dtype) { 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::its::getClustererSpec(useMC)); + specs.emplace_back(o2::itsmft::getITSClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::its::getClusterWriterSpec(useMC)); + if (!disableRootOutput || clrofOnly) { + specs.emplace_back(o2::itsmft::getITSClusterWriterSpec(useMC, doStag, clrofOnly)); } if ((trmode != TrackingMode::Off) && (TrackerParamConfig::Instance().trackingMode != TrackingMode::Off)) { - if (useCAtracker) { - if (useGPUWF) { - o2::gpu::GPURecoWorkflowSpec::Config cfg{ - .itsTriggerType = useTrig, - .processMC = useMC, - .runITSTracking = true, - .itsOverrBeamEst = overrideBeamPosition, - }; + if (useGPUWF) { + o2::gpu::GPURecoWorkflowSpec::Config cfg{ + .itsTriggerType = useTrig, + .processMC = useMC, + .runITSTracking = true, + .itsStaggered = doStag, + .itsOverrBeamEst = overrideBeamPosition, + }; - Inputs ggInputs; - auto ggRequest = std::make_shared(false, true, false, true, true, - useGeom ? o2::base::GRPGeomRequest::Aligned : o2::base::GRPGeomRequest::None, - ggInputs, true); - if (!useGeom) { - ggRequest->addInput({"itsTGeo", "ITS", "GEOMTGEO", 0, Lifetime::Condition, framework::ccdbParamSpec("ITS/Config/Geometry")}, ggInputs); - } + Inputs ggInputs; + auto ggRequest = std::make_shared(false, true, false, true, true, + useGeom ? o2::base::GRPGeomRequest::Aligned : o2::base::GRPGeomRequest::None, + ggInputs, true); + if (!useGeom) { + ggRequest->addInput({"itsTGeo", "ITS", "GEOMTGEO", 0, Lifetime::Condition, framework::ccdbParamSpec("ITS/Config/Geometry")}, ggInputs); + } - static std::vector policyData; - static std::shared_ptr task = std::make_shared(&policyData, cfg, std::vector(), 0, ggRequest); - Inputs taskInputs = task->inputs(); - Options taskOptions = task->options(); - std::move(ggInputs.begin(), ggInputs.end(), std::back_inserter(taskInputs)); + static std::vector policyData; + static std::shared_ptr task = std::make_shared(&policyData, cfg, std::vector(), 0, ggRequest); + Inputs taskInputs = task->inputs(); + Options taskOptions = task->options(); + std::move(ggInputs.begin(), ggInputs.end(), std::back_inserter(taskInputs)); - specs.emplace_back(DataProcessorSpec{ - .name = "its-gpu-tracker", - .inputs = taskInputs, - .outputs = task->outputs(), - .algorithm = AlgorithmSpec{adoptTask(task)}, - .options = taskOptions}); - } else { - specs.emplace_back(o2::its::getTrackerSpec(useMC, useGeom, useTrig, trmode, overrideBeamPosition, dtype)); - } + specs.emplace_back(DataProcessorSpec{ + .name = "its-gpu-tracker", + .inputs = taskInputs, + .outputs = task->outputs(), + .algorithm = AlgorithmSpec{adoptTask(task)}, + .options = taskOptions}); } else { - specs.emplace_back(o2::its::getCookedTrackerSpec(useMC, useGeom, useTrig, trmode)); + 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/ThresholdCalibratorSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/ThresholdCalibratorSpec.cxx index e1d7dc725e9e3..ce0b840f4a037 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/ThresholdCalibratorSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/ThresholdCalibratorSpec.cxx @@ -74,6 +74,8 @@ void ITSThresholdCalibrator::init(InitContext& ic) LOG(warning) << "mColStep = " << mColStep << ": saving s-curves of only 1 pixel (pix 0) per row"; } + isLocal = ic.options().get("local-processing"); + std::string fittype = ic.options().get("fittype"); if (fittype == "derivative") { this->mFitType = DERIVATIVE; @@ -149,6 +151,12 @@ void ITSThresholdCalibrator::init(InitContext& ic) // Parameters to operate in manual mode (when run type is not recognized automatically) isManualMode = ic.options().get("manual-mode"); if (isManualMode) { + try { + manualRowScan = ic.options().get("manual-rowscan"); + } catch (std::exception const& e) { + throw std::runtime_error("Number of scanned rows not found, mandatory in manual mode"); + } + try { manualMin = ic.options().get("manual-min"); } catch (std::exception const& e) { @@ -746,39 +754,40 @@ void ITSThresholdCalibrator::extractThresholdRow(const short int& chipID, const } else { // threshold, vcasn, ithr, vresetd_2d - short int iRU = getRUID(chipID); + for (int scan_i = 0; scan_i < ((mScanType == 'r') ? N_RANGE : N_RANGE2); scan_i++) { #ifdef WITH_OPENMP - omp_set_num_threads(mNThreads); + omp_set_num_threads(mNThreads); #pragma omp parallel for schedule(dynamic) #endif - // Loop over all columns (pixels) in the row - for (short int col_i = 0; col_i < this->N_COL; col_i++) { - // Do the threshold fit - float thresh = 0., noise = 0.; - bool success = false; - int spoints = 0; - int scan_i = mScanType == 'r' ? (mLoopVal[iRU][row] - mMin) / mStep : 0; - if (isDumpS) { // already protected for multi-thread in the init - mFitHist->SetName(Form("scurve_chip%d_row%d_col%d_scani%d", chipID, row, col_i, scan_i)); - } + // Loop over all columns (pixels) in the row + for (short int col_i = 0; col_i < this->N_COL; col_i++) { + // Do the threshold fit + float thresh = 0., noise = 0.; + bool success = false; + int spoints = 0; - success = this->findThreshold(chipID, mPixelHits[chipID][row][col_i], - this->mX, mScanType == 'r' ? N_RANGE2 : N_RANGE, thresh, noise, spoints, scan_i); + if (isDumpS) { // already protected for multi-thread in the init + mFitHist->SetName(Form("scurve_chip%d_row%d_col%d_scani%d", chipID, row, col_i, scan_i)); + } - vChipid[col_i] = chipID; - vRow[col_i] = row; - vThreshold[col_i] = (mScanType == 'T' || mScanType == 'r') ? (short int)(thresh * 10.) : (short int)(thresh); - vNoise[col_i] = (float)(noise * 10.); // always factor 10 also for ITHR/VCASN to not have all zeros - vSuccess[col_i] = success; - vPoints[col_i] = spoints > 0 ? (unsigned char)(spoints) : 0; + success = this->findThreshold(chipID, mPixelHits[chipID][row][col_i], + this->mX, mScanType == 'r' ? N_RANGE2 : N_RANGE, thresh, noise, spoints, scan_i); + vChipid[col_i] = chipID; + vRow[col_i] = row; + vThreshold[col_i] = (mScanType == 'T' || mScanType == 'r') ? (short int)(thresh * 10.) : (short int)(thresh); + vNoise[col_i] = (float)(noise * 10.); // always factor 10 also for ITHR/VCASN to not have all zeros + vSuccess[col_i] = success; + vPoints[col_i] = spoints > 0 ? (unsigned char)(spoints) : 0; + + if (mScanType == 'r') { + vMixData[col_i] = (scan_i * mStep) + mMin; + } + } if (mScanType == 'r') { - vMixData[col_i] = mLoopVal[iRU][row]; + this->saveThreshold(); // save before moving to the next vresetd } } - if (mScanType == 'r') { - this->saveThreshold(); // save before moving to the next vresetd - } // Fill the ScTree tree if (mScanType == 'T' || mScanType == 'V' || mScanType == 'I') { // TODO: store also for other scans? @@ -794,6 +803,9 @@ void ITSThresholdCalibrator::extractThresholdRow(const short int& chipID, const // Saves threshold information to internal memory if (mScanType != 'P' && mScanType != 'p' && mScanType != 't' && mScanType != 'R' && mScanType != 'r') { + if (mVerboseOutput) { + LOG(info) << "Saving data of ChipID: " << chipID << " Row: " << row; + } this->saveThreshold(); } } @@ -934,6 +946,7 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) this->mMax = 50; this->N_RANGE = 51; this->mCheckExactRow = true; + mRowScan = 512; } else if (runtype == THR_SCAN_SHORT || runtype == THR_SCAN_SHORT_100HZ || runtype == THR_SCAN_SHORT_200HZ || runtype == THR_SCAN_SHORT_33 || runtype == THR_SCAN_SHORT_2_10HZ || runtype == THR_SCAN_SHORT_150INJ) { @@ -951,6 +964,12 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) nInjScaled = nInj / 3; } } + mRowScan = 11; + if (runtype == THR_SCAN_SHORT_33) { + mRowScan = 33; + } else if (runtype == THR_SCAN_SHORT_2_10HZ) { + mRowScan = 2; + } } else if (runtype == VCASN150 || runtype == VCASN100 || runtype == VCASN100_100HZ || runtype == VCASN130 || runtype == VCASNBB) { // VCASN tuning for different target thresholds // Store average VCASN for each chip into CCDB @@ -962,6 +981,7 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) this->mMax = inMaxVcasn; // 80 is the default this->N_RANGE = mMax - mMin + 1; this->mCheckExactRow = true; + mRowScan = 4; } else if (runtype == ITHR150 || runtype == ITHR100 || runtype == ITHR100_100HZ || runtype == ITHR130) { // ITHR tuning -- average ITHR per chip @@ -973,6 +993,7 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) this->mMax = inMaxIthr; // 100 is the default this->N_RANGE = mMax - mMin + 1; this->mCheckExactRow = true; + mRowScan = 4; } else if (runtype == DIGITAL_SCAN || runtype == DIGITAL_SCAN_100HZ || runtype == DIGITAL_SCAN_NOMASK) { // Digital scan -- only storing one value per chip, no fit needed @@ -983,6 +1004,7 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) this->mMax = 0; this->N_RANGE = mMax - mMin + 1; this->mCheckExactRow = false; + mRowStep = 1; } else if (runtype == ANALOGUE_SCAN) { // Analogue scan -- only storing one value per chip, no fit needed @@ -1078,6 +1100,7 @@ void ITSThresholdCalibrator::setRunType(const short int& runtype) if (scaleNinj) { nInjScaled = nInj / 3; } + mRowScan = manualRowScan; } else { throw runtype; } @@ -1284,7 +1307,7 @@ void ITSThresholdCalibrator::extractAndUpdate(const short int& chipID, const sho return; } -////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// // Main running function // Get info from previous stf decoder workflow, then loop over readout frames // (ROFs) to count hits and extract thresholds @@ -1367,15 +1390,13 @@ void ITSThresholdCalibrator::run(ProcessingContext& pc) row = !mCdwVersion ? (short int)(calib.calibUserField & 0xffff) : (short int)(calib.calibUserField & 0x1ff); // cw counter cwcnt = (short int)(calib.calibCounter); - // count the last N injections - short int checkVal = (mScanType == 'I') ? mMin : mMax; - if ((mScanType != 'r' && mScanType != 'p' && mScanType != 't' && loopval == checkVal) || - (mScanType == 'r' && realcharge == mMax2) || - (mScanType == 'p' && realcharge == mMin2) || - (mScanType == 't' && loopval == checkVal && realcharge == mMax2)) { - mCdwCntRU[iRU][row]++; - mLoopVal[iRU][row] = loopval; // keep loop val (relevant for VRESET2D and TOT_1ROW scan only) - } + + // count injections + short int loopPoint = (loopval - this->mMin) / mStep; + short int chgPoint = (realcharge - this->mMin2) / mStep2; + auto& arr = mCdwCntRU[iRU][row]; + arr[chgPoint][loopPoint]++; + if (this->mVerboseOutput) { LOG(info) << "RU: " << iRU << " CDWcounter: " << cwcnt << " row: " << row << " Loopval: " << loopval << " realcharge: " << realcharge << " confDBv: " << mCdwVersion; LOG(info) << "NDIGITS: " << digits.size(); @@ -1470,15 +1491,13 @@ void ITSThresholdCalibrator::run(ProcessingContext& pc) if (ruIndex < 0) { continue; } - short int nL = 0; - for (int iL = 0; iL < 3; iL++) { - if (mActiveLinks[ruIndex][iL]) { - nL++; // count active links - } + short int nL = getNumberOfActiveLinks(mActiveLinks[ruIndex]); + if (isLocal) { + nL = ruIndex > 47 ? 2 : 3; } std::vector chipEnabled = getChipListFromRu(ruIndex, mActiveLinks[ruIndex]); // chip boundaries // Fill the chipDone info string - if (mRunTypeRUCopy[ruIndex] == nInjScaled * nL) { + if (mRunTypeRUCopy[ruIndex] == nInjScaled * nL && nL > 0) { for (short int iChip = 0; iChip < chipEnabled.size(); iChip++) { if ((chipEnabled[iChip] % mChipModBase) != mChipModSel) { continue; @@ -1488,14 +1507,37 @@ void ITSThresholdCalibrator::run(ProcessingContext& pc) mRunTypeRUCopy[ruIndex] = 0; // reset here is safer (the other counter is reset in finalize) } // Check if scan of a row is finished: only for specific scans! - bool passCondition = (mCdwCntRU[ruIndex][row] >= nInjScaled * nL); - if (mScanType == 'p' || mScanType == 't') { - passCondition = passCondition && (mLoopVal[ruIndex][row] == mMax); - if (mVerboseOutput) { - LOG(info) << "PassCondition: " << passCondition << " - (mCdwCntRU,mLoopVal) of RU" << ruIndex << " row " << row << " = (" << mCdwCntRU[ruIndex][row] << ", " << mLoopVal[ruIndex][row] << ")"; + bool passCondition = nL > 0 ? true : false; + for (int j1 = 0; j1 < N_RANGE2; j1++) { + for (int j2 = 0; j2 < N_RANGE; j2++) { + if (mScanType == 'D' || mScanType == 'A') { // D and A are processed in finalize and include >1 rows: row data can be mixed in time! + for (int ir = 0; ir < mRowScan; ir += mRowStep) { + if (!mCdwCntRU[ruIndex].count(ir)) { + passCondition = false; + break; + } else if (mCdwCntRU[ruIndex][ir][j1][j2] < nInjScaled * nL) { + passCondition = false; + break; + } + } + } else if (mScanType == 't') { // ToT scan is done in specific ranges depending on charge (see ITSComm) + if ((j1 == 0 && j2 < ((600 - mMin) / mStep)) || (j2 >= ((600 - mMin) / mStep) && j2 <= ((800 - mMin) / mStep)) || (j1 == 1 && j2 > ((800 - mMin) / mStep))) { + if (mCdwCntRU[ruIndex][row][j1][j2] < nInjScaled * nL) { + passCondition = false; + break; + } + } + } else if (mCdwCntRU[ruIndex][row][j1][j2] < nInjScaled * nL) { + passCondition = false; + break; + } + } + if (!passCondition) { + break; } - } else if (mVerboseOutput) { - LOG(info) << "PassCondition: " << passCondition << " - mCdwCntRU of RU" << ruIndex << " row " << row << " = " << mCdwCntRU[ruIndex][row]; + } + if (mVerboseOutput) { + LOG(info) << "PassCondition: " << passCondition << " - mCdwCntRU of RU" << ruIndex << " row " << row << " = " << mCdwCntRU[ruIndex][row][0][0] << "(Links: " << mActiveLinks[ruIndex][0] << ", " << mActiveLinks[ruIndex][1] << "," << mActiveLinks[ruIndex][2] << ")"; } if (mScanType != 'D' && mScanType != 'A' && mScanType != 'P' && mScanType != 'R' && passCondition) { @@ -1509,32 +1551,48 @@ void ITSThresholdCalibrator::run(ProcessingContext& pc) if (mPixelHits.count(chipID)) { if (mPixelHits[chipID].count(row)) { // make sure the row exists extractAndUpdate(chipID, row); - if (mScanType != 'p' && (mScanType != 'r' || mLoopVal[ruIndex][row] == mMax)) { // do not erase for scantype = p because in finalize() we have calculate2Dparams + if (mScanType != 'p') { // do not erase for scantype = p because in finalize() we have calculate2Dparams mPixelHits[chipID].erase(row); } } } } } - mCdwCntRU[ruIndex][row] = 0; // reset + mCdwCntRU[ruIndex].erase(row); // row is gone + } + + if (mRunTypeRU[ruIndex] >= nInjScaled * nL && passCondition) { + mFlagsRU[ruIndex] = true; + finalize(); + // Reset Active Links, mRunTypeRU, mFlagsRU (needed only for local data replay!) + if (mVerboseOutput) { + LOG(info) << "Resetting links of RU " << ruIndex; + } + if (!isLocal) { + mActiveLinks[ruIndex][0] = 0; + mActiveLinks[ruIndex][1] = 0; + mActiveLinks[ruIndex][2] = 0; + mRunTypeRU[ruIndex] = 0; + mFlagsRU[ruIndex] = false; + mCdwCntRU.erase(ruIndex); // for D,A,P,R (not entering the if above) + } + + LOG(info) << "Shipping all outputs to aggregator (before endOfStream arrival!)"; + pc.outputs().snapshot(Output{"ITS", "TSTR", (unsigned int)mChipModSel}, this->mTuning); + pc.outputs().snapshot(Output{"ITS", "PIXTYP", (unsigned int)mChipModSel}, this->mPixStat); + pc.outputs().snapshot(Output{"ITS", "RUNT", (unsigned int)mChipModSel}, this->mRunType); + pc.outputs().snapshot(Output{"ITS", "SCANT", (unsigned int)mChipModSel}, this->mScanType); + pc.outputs().snapshot(Output{"ITS", "FITT", (unsigned int)mChipModSel}, this->mFitType); + pc.outputs().snapshot(Output{"ITS", "CONFDBV", (unsigned int)mChipModSel}, this->mConfDBv); + pc.outputs().snapshot(Output{"ITS", "QCSTR", (unsigned int)mChipModSel}, this->mChipDoneQc); + // reset the DCSconfigObject_t before next ship out + mTuning.clear(); + mPixStat.clear(); + mChipDoneQc.clear(); } } // for (ROFs) - if (!(this->mRunTypeUp)) { - finalize(); - LOG(info) << "Shipping all outputs to aggregator (before endOfStream arrival!)"; - pc.outputs().snapshot(Output{"ITS", "TSTR", (unsigned int)mChipModSel}, this->mTuning); - pc.outputs().snapshot(Output{"ITS", "PIXTYP", (unsigned int)mChipModSel}, this->mPixStat); - pc.outputs().snapshot(Output{"ITS", "RUNT", (unsigned int)mChipModSel}, this->mRunType); - pc.outputs().snapshot(Output{"ITS", "SCANT", (unsigned int)mChipModSel}, this->mScanType); - pc.outputs().snapshot(Output{"ITS", "FITT", (unsigned int)mChipModSel}, this->mFitType); - pc.outputs().snapshot(Output{"ITS", "CONFDBV", (unsigned int)mChipModSel}, this->mConfDBv); - pc.outputs().snapshot(Output{"ITS", "QCSTR", (unsigned int)mChipModSel}, this->mChipDoneQc); - // reset the DCSconfigObject_t before next ship out - mTuning.clear(); - mPixStat.clear(); - mChipDoneQc.clear(); - } else if (pc.transitionState() == TransitionHandlingState::Requested) { + if (pc.transitionState() == TransitionHandlingState::Requested) { LOG(info) << "Run stop requested during the scan, sending output to aggregator and then stopping to process new data"; mRunStopRequested = true; finalize(); // calculating average thresholds based on what's collected up to this moment @@ -1769,18 +1827,19 @@ void ITSThresholdCalibrator::finalize() if (mScanType == 'I') { // Only ITHR scan: assign default ITHR = 50 if chip has no avg ITHR for (auto& iRU : mRuSet) { - if (mRunTypeRU[iRU] >= nInjScaled * getNumberOfActiveLinks(mActiveLinks[iRU]) || mRunStopRequested) { + if (mFlagsRU[iRU] || mRunStopRequested) { std::vector chipList = getChipListFromRu(iRU, mActiveLinks[iRU]); for (size_t i = 0; i < chipList.size(); i++) { if ((chipList[i] % mChipModBase) != mChipModSel) { continue; } - if (!mThresholds.count(chipList[i])) { + if (!mThresholds.count(chipList[i]) && !isChipDB[chipList[i]]) { if (mVerboseOutput) { LOG(info) << "Setting ITHR = 50 for chip " << chipList[i]; } std::vector data = {50, 0, 0, 0, 0}; addDatabaseEntry(chipList[i], name, data, false); + isChipDB[chipList[i]] = true; } } } @@ -1790,7 +1849,7 @@ void ITSThresholdCalibrator::finalize() auto it = this->mThresholds.cbegin(); while (it != this->mThresholds.cend()) { short int iRU = getRUID(it->first); - if (!isCRUITS && (mRunTypeRU[iRU] < nInjScaled * getNumberOfActiveLinks(mActiveLinks[iRU]) && !mRunStopRequested)) { + if (!isCRUITS && (!mFlagsRU[iRU]) && !mRunStopRequested) { ++it; continue; } @@ -1805,7 +1864,7 @@ void ITSThresholdCalibrator::finalize() if (mVerboseOutput) { LOG(info) << "Average or mpv " << name << " of chip " << it->first << " = " << outVal << " e-"; } - float status = ((float)it->second[4] / (float)(it->second[4] + it->second[5])) * 100.; // percentage of successful threshold extractions + float status = ((float)it->second[4] / (float)(mRowScan * N_COL)) * 100.; // percentage of successful threshold extractions if (status < mPercentageCut && (mScanType == 'I' || mScanType == 'V')) { if (mScanType == 'I') { // default ITHR if percentage of success < mPercentageCut outVal = 50.; @@ -1822,6 +1881,7 @@ void ITSThresholdCalibrator::finalize() } std::vector data = {outVal, rmsT, avgN, rmsN, status}; this->addDatabaseEntry(it->first, name, data, false); + isChipDB[it->first] = true; it = this->mThresholds.erase(it); } } else if (this->mScanType == 'D' || this->mScanType == 'A') { @@ -1831,7 +1891,7 @@ void ITSThresholdCalibrator::finalize() auto itchip = this->mPixelHits.cbegin(); while (itchip != this->mPixelHits.cend()) { // loop over chips collected short int iRU = getRUID(itchip->first); - if (!isCRUITS && (mRunTypeRU[iRU] < nInjScaled * getNumberOfActiveLinks(mActiveLinks[iRU]) && !mRunStopRequested)) { + if (!isCRUITS && !mFlagsRU[iRU] && !mRunStopRequested) { ++itchip; continue; } @@ -1845,7 +1905,7 @@ void ITSThresholdCalibrator::finalize() if (this->mVerboseOutput) { LOG(info) << "Chip " << itchip->first << " hits extracted"; } - ++itchip; + itchip = mPixelHits.erase(itchip); } auto it = this->mNoisyPixID.cbegin(); @@ -1886,7 +1946,7 @@ void ITSThresholdCalibrator::finalize() auto itchip = this->mPixelHits.cbegin(); while (itchip != mPixelHits.cend()) { int iRU = getRUID(itchip->first); - if (!mRunStopRequested && mRunTypeRU[iRU] < nInjScaled * getNumberOfActiveLinks(mActiveLinks[iRU])) { + if (!mRunStopRequested && !mFlagsRU[iRU]) { ++itchip; continue; } @@ -1909,15 +1969,11 @@ void ITSThresholdCalibrator::finalize() if (this->mVerboseOutput) { LOG(info) << "Chip " << itchip->first << " hits extracted"; } - ++itchip; + itchip = mPixelHits.erase(itchip); } // reset RU counters so that the chips which are done will not appear again in the DCSConfigObject } - for (auto& ru : thisRUs) { - mRunTypeRU[ru] = 0; // reset - } - return; } @@ -1928,9 +1984,9 @@ void ITSThresholdCalibrator::endOfStream(EndOfStreamContext& ec) { if (!isEnded && !mRunStopRequested) { LOGF(info, "endOfStream report:", mSelfName); - if (isCRUITS) { - finalize(); - } + LOG(info) << "Calling finalize(), doing nothing if scan has properly ended, otherwise save partial data in ROOT trees as backup"; + finalize(); + this->finalizeOutput(); isEnded = true; } @@ -1943,6 +1999,8 @@ void ITSThresholdCalibrator::stop() { if (!isEnded) { LOGF(info, "stop() report:", mSelfName); + LOG(info) << "Calling finalize(), doing nothing if scan has properly ended, otherwise save partial data in ROOT trees as backup"; + finalize(); this->finalizeOutput(); isEnded = true; } @@ -1996,6 +2054,7 @@ DataProcessorSpec getITSThresholdCalibratorSpec(const ITSCalibInpConf& inpConf) {"manual-step2", VariantType::Int, 1, {"Step2 value: defines the steps between manual-min2 and manual-max2. Default is 1. Use only in manual mode"}}, {"manual-scantype", VariantType::String, "T", {"scan type, can be D, T, I, V, P, p: use only in manual mode"}}, {"manual-strobewindow", VariantType::Int, 5, {"strobe duration in clock cycles, default is 5 = 125 ns: use only in manual mode"}}, + {"manual-rowscan", VariantType::Int, 512, {"Number of ALPIDE rows scanned in the run: use only in manual mode"}}, {"save-tree", VariantType::Bool, false, {"Flag to save ROOT tree on disk: use only in manual mode"}}, {"scale-ninj", VariantType::Bool, false, {"Flag to activate the scale of the number of injects to be used to count hits from specific MEBs: use only in manual mode and in combination with --meb-select"}}, {"enable-mpv", VariantType::Bool, false, {"Flag to enable calculation of most-probable value in vcasn/ithr scans"}}, @@ -2009,7 +2068,8 @@ DataProcessorSpec getITSThresholdCalibratorSpec(const ITSCalibInpConf& inpConf) {"charge-b", VariantType::Int, 0, {"To use with --calculate-slope, it defines the charge (in DAC) for the 2nd point used for the slope calculation"}}, {"meb-select", VariantType::Int, -1, {"Select from which multi-event buffer consider the hits: 0,1 or 2"}}, {"s-curve-col-step", VariantType::Int, 8, {"save s-curves points to tree every s-curve-col-step pixels on 1 row"}}, - {"percentage-cut", VariantType::Int, 25, {"discard chip in ITHR/VCASN scan if the percentage of success is less than this cut"}}}}; + {"percentage-cut", VariantType::Int, 25, {"discard chip in ITHR/VCASN scan if the percentage of success is less than this cut"}}, + {"local-processing", VariantType::Bool, false, {"Enable in case of data replay of scans processed row by row or in 1 go in finalize() but with partial data in the raw TF (e.g. data dump stopped before the real end of run)"}}}}; } } // namespace its } // namespace o2 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 dbfd5edf839ae..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. // @@ -14,6 +14,8 @@ #include "Framework/ControlService.h" #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" @@ -25,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), + 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); } @@ -60,6 +63,14 @@ void TrackerDPL::run(ProcessingContext& pc) mITSTrackingInterface.run(pc); mTimer.Stop(); LOGP(info, "CPU Reconstruction time for this TF {:.2f} s (cpu), {:.2f} s (wall)", mTimer.CpuTime() - cput, mTimer.RealTime() - realt); + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::VertexerParamConfig::Instance().getName()), o2::its::VertexerParamConfig::Instance().getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::TrackerParamConfig::Instance().getName()), o2::its::TrackerParamConfig::Instance().getName()); + } + } } void TrackerDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) @@ -74,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) { @@ -114,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 b7d72eb3618db..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" @@ -41,7 +42,7 @@ void customize(std::vector& workflowOptions) {"clusters-from-upstream", o2::framework::VariantType::Bool, false, {"clusters will be provided from upstream, skip clusterizer"}}, {"disable-root-output", o2::framework::VariantType::Bool, false, {"do not write output root files"}}, {"disable-mc", o2::framework::VariantType::Bool, false, {"disable MC propagation even if available"}}, - {"trackerCA", o2::framework::VariantType::Bool, false, {"use trackerCA (default: trackerCM)"}}, + {"trackerCA", o2::framework::VariantType::Bool, false, {"use trackerCA (deprecated)"}}, // FIXME: keep this around to not break scripts {"ccdb-meanvertex-seed", o2::framework::VariantType::Bool, false, {"use MeanVertex from CCDB if available to provide beam position seed (default: false)"}}, {"select-with-triggers", o2::framework::VariantType::String, "none", {"use triggers to prescale processed ROFs: phys, trd, none"}}, {"tracking-mode", o2::framework::VariantType::String, "sync", {"sync,async,cosmics,unset,off"}}, @@ -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); } @@ -64,15 +67,16 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) // Update the (declared) parameters if changed from the command line auto useMC = !configcontext.options().get("disable-mc"); auto beamPosOVerride = configcontext.options().get("ccdb-meanvertex-seed"); - auto useCAtracker = configcontext.options().get("trackerCA"); auto trmode = configcontext.options().get("tracking-mode"); auto selTrig = configcontext.options().get("select-with-triggers"); auto useGpuWF = configcontext.options().get("use-gpu-workflow"); - auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); + 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"); 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"; } @@ -88,17 +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, - useCAtracker, - 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/base/include/MFTBase/GeometryTGeo.h b/Detectors/ITSMFT/MFT/base/include/MFTBase/GeometryTGeo.h index 503e8332c4cf5..20b5407d614c5 100644 --- a/Detectors/ITSMFT/MFT/base/include/MFTBase/GeometryTGeo.h +++ b/Detectors/ITSMFT/MFT/base/include/MFTBase/GeometryTGeo.h @@ -95,7 +95,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo Int_t getSensorIndex(Int_t half, Int_t disk, Int_t ladder, Int_t sensor) const; /// get layer index (0:9) from the chip index - Int_t getLayer(Int_t index) const; + Int_t getLayer(Int_t index) const final; /// This routine computes the half, disk, ladder and sensor number /// given the sensor index number @@ -122,7 +122,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo { return extractNumberOfDisks(half); } - /// Returns the number of halfs MFT + /// Returns the number of halves MFT Int_t getNumberOfHalfs() { return extractNumberOfHalves(); @@ -181,7 +181,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo Int_t extractVolumeCopy(const Char_t* name, const Char_t* prefix) const; /// Get the transformation matrix of the sensor [...] - /// for a given sensor 'index' by quering the TGeoManager + /// for a given sensor 'index' by querying the TGeoManager TGeoHMatrix* extractMatrixSensor(Int_t index) const; // Create matrix for transformation from sensor local frame to global one diff --git a/Detectors/ITSMFT/MFT/calibration/include/MFTCalibration/NoiseSlotCalibrator.h b/Detectors/ITSMFT/MFT/calibration/include/MFTCalibration/NoiseSlotCalibrator.h deleted file mode 100644 index a8280467b14c9..0000000000000 --- a/Detectors/ITSMFT/MFT/calibration/include/MFTCalibration/NoiseSlotCalibrator.h +++ /dev/null @@ -1,101 +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 NoiseSlotCalibrator.h - -#ifndef O2_MFT_NOISESLOTCALIBRATOR -#define O2_MFT_NOISESLOTCALIBRATOR - -#include - -#include "DetectorsCalibration/TimeSlotCalibration.h" -#include "DetectorsCalibration/TimeSlot.h" - -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/NoiseMap.h" -#include "gsl/span" - -namespace o2 -{ - -namespace itsmft -{ -class ROFRecord; -} // namespace itsmft - -namespace mft -{ - -class NoiseSlotCalibrator : public o2::calibration::TimeSlotCalibration -{ - using Slot = calibration::TimeSlot; - - public: - NoiseSlotCalibrator() { setUpdateAtTheEndOfRunOnly(); } - NoiseSlotCalibrator(float prob, float relErr) : mProbabilityThreshold(prob), mProbRelErr(relErr) - { - setUpdateAtTheEndOfRunOnly(); - setSlotLength(INFINITE_TF); - mMinROFs = 1.1 * o2::itsmft::NoiseMap::getMinROFs(prob, relErr); - LOGP(info, "At least {} ROFs needed to apply threshold {} with relative error {}", mMinROFs, mProbabilityThreshold, mProbRelErr); - } - ~NoiseSlotCalibrator() final = default; - - void setThreshold(unsigned int t) { mThreshold = t; } - - bool processTimeFrame(calibration::TFType tf, - gsl::span const& digits, - gsl::span const& rofs); - - bool processTimeFrame(calibration::TFType tf, - gsl::span const& clusters, - gsl::span const& patterns, - gsl::span const& rofs); - - void setMinROFs(long n) { mMinROFs = n; } - - void finalize() - { - LOG(info) << "Number of processed strobes is " << mNumberOfStrobes; - auto& slot = getSlots().back(); - slot.getContainer()->applyProbThreshold(mProbabilityThreshold, mNumberOfStrobes); - } - - const o2::itsmft::NoiseMap& getNoiseMap(long& start, long& end) - { - const auto& slot = getSlots().back(); - start = slot.getTFStart(); - end = slot.getTFEnd(); - return *(slot.getContainer()); - } - - // Functions overloaded from the calibration framework - bool process(calibration::TFType tf, const gsl::span data) final; - - // Functions required by the calibration framework - void initOutput() final {} - Slot& emplaceNewSlot(bool, calibration::TFType, calibration::TFType) final; - void finalizeSlot(Slot& slot) final; - bool hasEnoughData(const Slot& slot) const final; - - private: - float mProbabilityThreshold = 1e-6f; - float mProbRelErr = 0.2; // relative error on channel noise to apply the threshold - long mMinROFs = 0; - unsigned int mThreshold = 100; - unsigned int mNumberOfStrobes = 0; -}; - -} // namespace mft -} // namespace o2 - -#endif /* O2_MFT_NOISESLOTCALIBRATOR */ diff --git a/Detectors/ITSMFT/MFT/calibration/src/MchAlignment.cxx b/Detectors/ITSMFT/MFT/calibration/src/MchAlignment.cxx deleted file mode 100644 index b9e590cca0b63..0000000000000 --- a/Detectors/ITSMFT/MFT/calibration/src/MchAlignment.cxx +++ /dev/null @@ -1,1660 +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 Alignment -/// Alignment class for the ALICE DiMuon spectrometer -/// -/// MUON specific alignment class which interface to AliMillepede. -/// For each track ProcessTrack calculates the local and global derivatives -/// at each cluster and fill the corresponding local equations. Provide methods -/// for fixing or constraining detection elements for best results. -/// -/// \author Javier Castillo Castellanos -//----------------------------------------------------------------------------- - -#include "MCHAlign/Alignment.h" -#include "MCHAlign/MillePede2.h" -#include "MCHAlign/MillePedeRecord.h" -#include - -#include "MCHTracking/Track.h" -#include "MCHTracking/TrackParam.h" -#include "MCHTracking/Cluster.h" -#include "TGeoManager.h" - -// #include "DataFormatsMCH/ROFRecord.h" -// #include "DataFormatsMCH/TrackMCH.h" -// #include "DataFormatsMCH/Cluster.h" -// #include "DataFormatsMCH/Digit.h" - -// #include "AliMUONGeometryTransformer.h" -// #include "AliMUONGeometryModuleTransformer.h" -// #include "MCHAlign/AliMUONGeometryDetElement.h" -// #include "AliMUONGeometryBuilder.h" -#include "MCHGeometryCreator/Geometry.h" -#include "MCHGeometryTest/Helpers.h" -#include "MCHGeometryTransformer/Transformations.h" -#include "TGeoManager.h" - -// #include "Align/Millepede2Record.h" //to be replaced -// #include "AliMpExMap.h" -// #include "AliMpExMapIterator.h" - -#include "DetectorsCommonDataFormats/AlignParam.h" -#include "Framework/Logger.h" - -#include -#include -#include -#include -#include -#include - -namespace o2 -{ -namespace mch -{ - -using namespace std; - -//_____________________________________________________________________ -// static variables -const Int_t Alignment::fgNDetElemCh[Alignment::fgNCh] = {4, 4, 4, 4, 18, 18, 26, 26, 26, 26}; -const Int_t Alignment::fgSNDetElemCh[Alignment::fgNCh + 1] = {0, 4, 8, 12, 16, 34, 52, 78, 104, 130, 156}; - -// number of detector elements in each half-chamber -const Int_t Alignment::fgNDetElemHalfCh[Alignment::fgNHalfCh] = {2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 13, 13, 13, 13, 13, 13, 13, 13}; - -// list of detector elements for each half chamber -const Int_t Alignment::fgDetElemHalfCh[Alignment::fgNHalfCh][Alignment::fgNDetHalfChMax] = - { - {100, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {101, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - - {200, 203, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {201, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - - {300, 303, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {301, 302, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - - {400, 403, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {401, 402, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - - {500, 501, 502, 503, 504, 514, 515, 516, 517, 0, 0, 0, 0}, - {505, 506, 507, 508, 509, 510, 511, 512, 513, 0, 0, 0, 0}, - - {600, 601, 602, 603, 604, 614, 615, 616, 617, 0, 0, 0, 0}, - {605, 606, 607, 608, 609, 610, 611, 612, 613, 0, 0, 0, 0}, - - {700, 701, 702, 703, 704, 705, 706, 720, 721, 722, 723, 724, 725}, - {707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719}, - - {800, 801, 802, 803, 804, 805, 806, 820, 821, 822, 823, 824, 825}, - {807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819}, - - {900, 901, 902, 903, 904, 905, 906, 920, 921, 922, 923, 924, 925}, - {907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919}, - - {1000, 1001, 1002, 1003, 1004, 1005, 1006, 1020, 1021, 1022, 1023, 1024, 1025}, - {1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019} - -}; - -//_____________________________________________________________________ -/// self initialized array, used for adding constraints -class Array -{ - - public: - /// contructor - Array(void) - { - for (Int_t i = 0; i < Alignment::fNGlobal; ++i) { - values[i] = 0; - } - } - - /// array - Double_t values[Alignment::fNGlobal]; - - private: - /// Not implemented - Array(const Array&); - - /// Not implemented - Array& operator=(const Array&); -}; - -//________________________________________________________________________ -Double_t Square(Double_t x) { return x * x; } - -//_____________________________________________________________________ -Alignment::Alignment() - : TObject(), - fInitialized(kFALSE), - fRunNumber(0), - fBFieldOn(kFALSE), - fRefitStraightTracks(kFALSE), - fStartFac(256), - fResCutInitial(100), - fResCut(100), - fMillepede(0L), // to be modified - fCluster(0L), - fNStdDev(3), - fDetElemNumber(0), - fTrackRecord(), - fTransformCreator(), - fGeoCombiTransInverse(), - fDoEvaluation(kFALSE), - fTrackParamOrig(0), - fTrackParamNew(0), - fTFile(0), - fTTree(0) -{ - /// constructor - fSigma[0] = 1.5e-1; - fSigma[1] = 1.0e-2; - - // default allowed variations - fAllowVar[0] = 0.5; // x - fAllowVar[1] = 0.5; // y - fAllowVar[2] = 0.01; // phi_z - fAllowVar[3] = 5; // z - - // initialize millepede - fMillepede = new MillePede2(); - // fMillepede = new o2::align::Mille("theMilleFile.txt"); // To be replaced by MillePede2 - - // initialize degrees of freedom - // by default all parameters are free - for (Int_t iPar = 0; iPar < fNGlobal; ++iPar) { - fGlobalParameterStatus[iPar] = kFreeParId; - } - - // initialize local equations - for (int i = 0; i < fNLocal; ++i) { - fLocalDerivatives[i] = 0.0; - } - - for (int i = 0; i < fNGlobal; ++i) { - fGlobalDerivatives[i] = 0.0; - } -} - -//_____________________________________________________________________ -// Alignment::~Alignment() -//{ -// /// destructor -//} -// Alignment::~Alignment() = default; -//_____________________________________________________________________ -void Alignment::init(void) -{ - - /// initialize - /** - initialize millipede - must be called after necessary detectors have been fixed, - but before constrains are added and before global parameters initial value are set - */ - if (fInitialized) { - LOG(fatal) << "Millepede already initialized"; - } - - // assign proper groupID to free parameters - Int_t nGlobal = 0; - for (Int_t iPar = 0; iPar < fNGlobal; ++iPar) { - - if (fGlobalParameterStatus[iPar] == kFixedParId) { - // fixed parameters are left unchanged - continue; - - } else if (fGlobalParameterStatus[iPar] == kFreeParId || fGlobalParameterStatus[iPar] == kGroupBaseId) { - - // free parameters or first element of group are assigned a new group id - fGlobalParameterStatus[iPar] = nGlobal++; - continue; - - } else if (fGlobalParameterStatus[iPar] < kGroupBaseId) { - - // get detector element id from status, get chamber parameter id - const Int_t iDeBase(kGroupBaseId - 1 - fGlobalParameterStatus[iPar]); - const Int_t iParBase = iPar % fgNParCh; - - // check - if (iDeBase < 0 || iDeBase >= iPar / fgNParCh) { - LOG(fatal) << "Group for parameter index " << iPar << " has wrong base detector element: " << iDeBase; - } - - // assign identical group id to current - fGlobalParameterStatus[iPar] = fGlobalParameterStatus[iDeBase * fgNParCh + iParBase]; - LOG(info) << "Parameter " << iPar << " grouped to detector " << iDeBase << " (" << GetParameterMaskString(1 << iParBase).Data() << ")"; - - } else - LOG(fatal) << "Unrecognized parameter status for index " << iPar << ": " << fGlobalParameterStatus[iPar]; - } - - LOG(info) << "Free Parameters: " << nGlobal << " out of " << fNGlobal; - - // initialize millepede - // fMillepede->InitMille(fNGlobal, fNLocal, fNStdDev, fResCut, fResCutInitial, fGlobalParameterStatus); - fMillepede->InitMille(fNGlobal, fNLocal, fNStdDev, fResCut, fResCutInitial); // MillePede2 implementation - - fInitialized = kTRUE; - - // some debug output - for (Int_t iPar = 0; iPar < fgNParCh; ++iPar) { - LOG(info) << "fAllowVar[" << iPar << "]= " << fAllowVar[iPar]; - } - - // set allowed variations for all parameters - for (Int_t iDet = 0; iDet < fgNDetElem; ++iDet) { - for (Int_t iPar = 0; iPar < fgNParCh; ++iPar) { - fMillepede->SetParSigma(iDet * fgNParCh + iPar, fAllowVar[iPar]); - } - } - - // Set iterations - if (fStartFac > 1) { - fMillepede->SetIterations(fStartFac); - } - // setup monitoring TFile - if (fDoEvaluation && fRefitStraightTracks) { - fTFile = new TFile("Alignment.root", "RECREATE"); - fTTree = new TTree("TreeE", "Evaluation"); - - const Int_t kSplitlevel = 98; - const Int_t kBufsize = 32000; - - fTrackParamOrig = new LocalTrackParam(); - fTTree->Branch("fTrackParamOrig", "LocalTrackParam", &fTrackParamOrig, kBufsize, kSplitlevel); - - fTrackParamNew = new LocalTrackParam(); - fTTree->Branch("fTrackParamNew", "LocalTrackParam", &fTrackParamNew, kBufsize, kSplitlevel); - } -} - -//_____________________________________________________ -void Alignment::terminate(void) -{ - LOG(info) << "Closing Evaluation TFile"; - if (fTFile && fTTree) { - fTFile->cd(); - fTTree->Write(); - fTFile->Close(); - } -} - -//_____________________________________________________ -MillePedeRecord* Alignment::ProcessTrack(Track& track, Bool_t doAlignment, Double_t weight) -{ - /// process track for alignment minimization - /** - returns the alignment records for this track. - They can be stored in some output for later reprocessing. - */ - - // reset track records - fTrackRecord.Reset(); - if (fMillepede->GetRecord()) { - fMillepede->GetRecord()->Reset(); - } - - // loop over clusters to get starting values - Bool_t first(kTRUE); - // if (!trackParam) - // continue; - for (auto itTrackParam(track.begin()); itTrackParam != track.end(); ++itTrackParam) { - - // get cluster - const Cluster* Cluster = itTrackParam->getClusterPtr(); - if (!cluster) - continue; - - // for first valid cluster, save track position as "starting" values - if (first) { - - first = kFALSE; - FillTrackParamData(&*itTrackParam); - fTrackPos0[0] = fTrackPos[0]; - fTrackPos0[1] = fTrackPos[1]; - fTrackPos0[2] = fTrackPos[2]; - fTrackSlope0[0] = fTrackSlope[0]; - fTrackSlope0[1] = fTrackSlope[1]; - - break; - } - } - - // redo straight track fit - if (fRefitStraightTracks) { - - // refit straight track - const LocalTrackParam trackParam(RefitStraightTrack(track, fTrackPos0[2])); - - // fill evaluation tree - if (fTrackParamOrig) { - fTrackParamOrig->fTrackX = fTrackPos0[0]; - fTrackParamOrig->fTrackY = fTrackPos0[1]; - fTrackParamOrig->fTrackZ = fTrackPos0[2]; - fTrackParamOrig->fTrackSlopeX = fTrackSlope[0]; - fTrackParamOrig->fTrackSlopeY = fTrackSlope[1]; - } - - // new ones - if (fTrackParamNew) { - fTrackParamNew->fTrackX = trackParam.fTrackX; - fTrackParamNew->fTrackY = trackParam.fTrackY; - fTrackParamNew->fTrackZ = trackParam.fTrackZ; - fTrackParamNew->fTrackSlopeX = trackParam.fTrackSlopeX; - fTrackParamNew->fTrackSlopeY = trackParam.fTrackSlopeY; - } - - if (fTTree) - fTTree->Fill(); - - /* - copy new parameters to stored ones for derivatives calculation - this is done only if BFieldOn is false, for which these parameters are used - */ - if (!fBFieldOn) { - fTrackPos0[0] = trackParam.fTrackX; - fTrackPos0[1] = trackParam.fTrackY; - fTrackPos0[2] = trackParam.fTrackZ; - fTrackSlope[0] = trackParam.fTrackSlopeX; - fTrackSlope[1] = trackParam.fTrackSlopeY; - } - } - - // second loop to perform alignment - for (auto itTrackParam(track.begin()); itTrackParam != track.end(); ++itTrackParam) { - - // get track parameters - if (!&*itTrackParam) - continue; - - // get cluster - const Cluster* cluster = itTrackParam->getClusterPtr(); - if (!cluster) - continue; - - // fill local variables for this position --> one measurement - FillDetElemData(cluster); - FillRecPointData(cluster); - FillTrackParamData(&*itTrackParam); - - // 'inverse' (GlobalToLocal) rotation matrix - const Double_t* r(fGeoCombiTransInverse.GetRotationMatrix()); - - // calculate measurements - if (fBFieldOn) { - - // use residuals (cluster - track) for measurement - fMeas[0] = r[0] * (fClustPos[0] - fTrackPos[0]) + r[1] * (fClustPos[1] - fTrackPos[1]); - fMeas[1] = r[3] * (fClustPos[0] - fTrackPos[0]) + r[4] * (fClustPos[1] - fTrackPos[1]); - - } else { - - // use cluster position for measurement - fMeas[0] = (r[0] * fClustPos[0] + r[1] * fClustPos[1]); - fMeas[1] = (r[3] * fClustPos[0] + r[4] * fClustPos[1]); - } - - // Set local equations - LocalEquationX(); - LocalEquationY(); - } - - // copy track record - fMillepede->SetRecordRun(fRunNumber); - fMillepede->SetRecordWeight(weight); - fTrackRecord = *fMillepede->GetRecord(); - - // save record data - if (doAlignment) { - fMillepede->SaveRecordData(); - fMillepede->CloseDataRecStorage(); - } - - // return record - return &fTrackRecord; -} - -//______________________________________________________________________________ -void Alignment::ProcessTrack(MillePedeRecord* trackRecord) -{ - LOG(fatal) << __PRETTY_FUNCTION__ << " is disabled"; - - /// process track record - if (!trackRecord) - return; - - // // make sure record storage is initialized - if (!fMillepede->GetRecord()) { - fMillepede->InitDataRecStorage(kFalse); - } - // // copy content - *fMillepede->GetRecord() = *trackRecord; - - // save record - fMillepede->SaveRecordData(); - // write to local file - fMillepede->CloseDataRecStorage(); - - return; -} - -//_____________________________________________________________________ -void Alignment::FixAll(UInt_t mask) -{ - /// fix parameters matching mask, for all chambers - LOG(info) << "Fixing " << GetParameterMaskString(mask).Data() << " for all detector elements"; - - // fix all stations - for (Int_t i = 0; i < fgNDetElem; ++i) { - if (mask & ParX) - FixParameter(i, 0); - if (mask & ParY) - FixParameter(i, 1); - if (mask & ParTZ) - FixParameter(i, 2); - if (mask & ParZ) - FixParameter(i, 3); - } -} - -//_____________________________________________________________________ -void Alignment::FixChamber(Int_t iCh, UInt_t mask) -{ - /// fix parameters matching mask, for all detector elements in a given chamber, counting from 1 - - // check boundaries - if (iCh < 1 || iCh > 10) { - LOG(fatal) << "Invalid chamber index " << iCh; - } - - // get first and last element - const Int_t iDetElemFirst = fgSNDetElemCh[iCh - 1]; - const Int_t iDetElemLast = fgSNDetElemCh[iCh]; - for (Int_t i = iDetElemFirst; i < iDetElemLast; ++i) { - - LOG(info) << "Fixing " << GetParameterMaskString(mask).Data() << " for detector element " << i; - - if (mask & ParX) - FixParameter(i, 0); - if (mask & ParY) - FixParameter(i, 1); - if (mask & ParTZ) - FixParameter(i, 2); - if (mask & ParZ) - FixParameter(i, 3); - } -} - -//_____________________________________________________________________ -void Alignment::FixDetElem(Int_t iDetElemId, UInt_t mask) -{ - /// fix parameters matching mask, for a given detector element, counting from 0 - const Int_t iDet(GetDetElemNumber(iDetElemId)); - if (mask & ParX) - FixParameter(iDet, 0); - if (mask & ParY) - FixParameter(iDet, 1); - if (mask & ParTZ) - FixParameter(iDet, 2); - if (mask & ParZ) - FixParameter(iDet, 3); -} - -//_____________________________________________________________________ -void Alignment::FixHalfSpectrometer(const Bool_t* lChOnOff, UInt_t sidesMask, UInt_t mask) -{ - - /// Fix parameters matching mask for all detectors in selected chambers and selected sides of the spectrometer - for (Int_t i = 0; i < fgNDetElem; ++i) { - - // get chamber matching detector - const Int_t iCh(GetChamberId(i)); - if (!lChOnOff[iCh - 1]) - continue; - - // get detector element in chamber - Int_t lDetElemNumber = i - fgSNDetElemCh[iCh - 1]; - - // skip detector if its side is off - // stations 1 and 2 - if (iCh >= 1 && iCh <= 4) { - if (lDetElemNumber == 0 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber == 1 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber == 2 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber == 3 && !(sidesMask & SideBottomRight)) - continue; - } - - // station 3 - if (iCh >= 5 && iCh <= 6) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 4 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber >= 5 && lDetElemNumber <= 10 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber >= 11 && lDetElemNumber <= 13 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber >= 14 && lDetElemNumber <= 17 && !(sidesMask & SideBottomRight)) - continue; - } - - // stations 4 and 5 - if (iCh >= 7 && iCh <= 10) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 6 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber >= 7 && lDetElemNumber <= 13 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber >= 14 && lDetElemNumber <= 19 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber >= 20 && lDetElemNumber <= 25 && !(sidesMask & SideBottomRight)) - continue; - } - - // detector is accepted, fix it - FixDetElem(i, mask); - } -} - -//______________________________________________________________________ -void Alignment::FixParameter(Int_t iPar) -{ - - /// fix a given parameter, counting from 0 - if (fInitialized) { - LOG(fatal) << "Millepede already initialized"; - } - - fGlobalParameterStatus[iPar] = kFixedParId; -} - -//_____________________________________________________________________ -void Alignment::ReleaseChamber(Int_t iCh, UInt_t mask) -{ - /// release parameters matching mask, for all detector elements in a given chamber, counting from 1 - - // check boundaries - if (iCh < 1 || iCh > 10) { - LOG(fatal) << "Invalid chamber index " << iCh; - } - - // get first and last element - const Int_t iDetElemFirst = fgSNDetElemCh[iCh - 1]; - const Int_t iDetElemLast = fgSNDetElemCh[iCh]; - for (Int_t i = iDetElemFirst; i < iDetElemLast; ++i) { - - LOG(info) << "Releasing " << GetParameterMaskString(mask).Data() << " for detector element " << i; - - if (mask & ParX) - ReleaseParameter(i, 0); - if (mask & ParY) - ReleaseParameter(i, 1); - if (mask & ParTZ) - ReleaseParameter(i, 2); - if (mask & ParZ) - ReleaseParameter(i, 3); - } -} - -//_____________________________________________________________________ -void Alignment::ReleaseDetElem(Int_t iDetElemId, UInt_t mask) -{ - /// release parameters matching mask, for a given detector element, counting from 0 - const Int_t iDet(GetDetElemNumber(iDetElemId)); - if (mask & ParX) - ReleaseParameter(iDet, 0); - if (mask & ParY) - ReleaseParameter(iDet, 1); - if (mask & ParTZ) - ReleaseParameter(iDet, 2); - if (mask & ParZ) - ReleaseParameter(iDet, 3); -} - -//______________________________________________________________________ -void Alignment::ReleaseParameter(Int_t iPar) -{ - - /// release a given parameter, counting from 0 - if (fInitialized) { - LOG(fatal) << "Millepede already initialized"; - } - - fGlobalParameterStatus[iPar] = kFreeParId; -} - -//_____________________________________________________________________ -void Alignment::GroupChamber(Int_t iCh, UInt_t mask) -{ - /// group parameters matching mask for all detector elements in a given chamber, counting from 1 - if (iCh < 1 || iCh > fgNCh) { - LOG(fatal) << "Invalid chamber index " << iCh; - } - - const Int_t detElemMin = 100 * iCh; - const Int_t detElemMax = 100 * iCh + fgNDetElemCh[iCh] - 1; - GroupDetElems(detElemMin, detElemMax, mask); -} - -//_____________________________________________________________________ -void Alignment::GroupHalfChamber(Int_t iCh, Int_t iHalf, UInt_t mask) -{ - /// group parameters matching mask for all detector elements in a given tracking module (= half chamber), counting from 0 - if (iCh < 1 || iCh > fgNCh) { - LOG(fatal) << "Invalid chamber index " << iCh; - } - - if (iHalf < 0 || iHalf > 1) { - LOG(fatal) << "Invalid half chamber index " << iHalf; - } - - const Int_t iHalfCh = 2 * (iCh - 1) + iHalf; - GroupDetElems(&fgDetElemHalfCh[iHalfCh][0], fgNDetElemHalfCh[iHalfCh], mask); -} - -//_____________________________________________________________________ -void Alignment::GroupDetElems(Int_t detElemMin, Int_t detElemMax, UInt_t mask) -{ - /// group parameters matching mask for all detector elements between min and max - // check number of detector elements - const Int_t nDetElem = detElemMax - detElemMin + 1; - if (nDetElem < 2) { - LOG(fatal) << "Requested group of DEs " << detElemMin << "-" << detElemMax << " contains less than 2 DE's"; - } - - // create list - Int_t* detElemList = new int[nDetElem]; - for (Int_t i = 0; i < nDetElem; ++i) { - detElemList[i] = detElemMin + i; - } - - // group - GroupDetElems(detElemList, nDetElem, mask); - delete[] detElemList; -} - -//_____________________________________________________________________ -void Alignment::GroupDetElems(const Int_t* detElemList, Int_t nDetElem, UInt_t mask) -{ - /// group parameters matching mask for all detector elements in list - if (fInitialized) { - LOG(fatal) << "Millepede already initialized"; - } - - const Int_t iDeBase(GetDetElemNumber(detElemList[0])); - for (Int_t i = 0; i < nDetElem; ++i) { - const Int_t iDeCurrent(GetDetElemNumber(detElemList[i])); - if (mask & ParX) - fGlobalParameterStatus[iDeCurrent * fgNParCh + 0] = (i == 0) ? kGroupBaseId : (kGroupBaseId - iDeBase - 1); - if (mask & ParY) - fGlobalParameterStatus[iDeCurrent * fgNParCh + 1] = (i == 0) ? kGroupBaseId : (kGroupBaseId - iDeBase - 1); - if (mask & ParTZ) - fGlobalParameterStatus[iDeCurrent * fgNParCh + 2] = (i == 0) ? kGroupBaseId : (kGroupBaseId - iDeBase - 1); - if (mask & ParZ) - fGlobalParameterStatus[iDeCurrent * fgNParCh + 3] = (i == 0) ? kGroupBaseId : (kGroupBaseId - iDeBase - 1); - - if (i == 0) - LOG(info) << "Creating new group for detector " << detElemList[i] << " and variable " << GetParameterMaskString(mask).Data(); - else - LOG(info) << "Adding detector element " << detElemList[i] << " to current group"; - } -} - -//______________________________________________________________________ -void Alignment::SetChamberNonLinear(Int_t iCh, UInt_t mask) -{ - /// Set parameters matching mask as non linear, for all detector elements in a given chamber, counting from 1 - const Int_t iDetElemFirst = fgSNDetElemCh[iCh - 1]; - const Int_t iDetElemLast = fgSNDetElemCh[iCh]; - for (Int_t i = iDetElemFirst; i < iDetElemLast; ++i) { - - if (mask & ParX) - SetParameterNonLinear(i, 0); - if (mask & ParY) - SetParameterNonLinear(i, 1); - if (mask & ParTZ) - SetParameterNonLinear(i, 2); - if (mask & ParZ) - SetParameterNonLinear(i, 3); - } -} - -//_____________________________________________________________________ -void Alignment::SetDetElemNonLinear(Int_t iDetElemId, UInt_t mask) -{ - /// Set parameters matching mask as non linear, for a given detector element, counting from 0 - const Int_t iDet(GetDetElemNumber(iDetElemId)); - if (mask & ParX) - SetParameterNonLinear(iDet, 0); - if (mask & ParY) - SetParameterNonLinear(iDet, 1); - if (mask & ParTZ) - SetParameterNonLinear(iDet, 2); - if (mask & ParZ) - SetParameterNonLinear(iDet, 3); -} - -//______________________________________________________________________ -void Alignment::SetParameterNonLinear(Int_t iPar) -{ - /// Set nonlinear flag for parameter iPar - if (!fInitialized) { - LOG(fatal) << "Millepede not initialized"; - } - - fMillepede->SetNonLinear(iPar); - LOG(info) << "Parameter " << iPar << " set to non linear "; -} - -//______________________________________________________________________ -void Alignment::AddConstraints(const Bool_t* lChOnOff, UInt_t mask) -{ - /// Add constraint equations for selected chambers and degrees of freedom - - Array fConstraintX; - Array fConstraintY; - Array fConstraintTZ; - Array fConstraintZ; - - for (Int_t i = 0; i < fgNDetElem; ++i) { - - // get chamber matching detector - const Int_t iCh(GetChamberId(i)); - if (lChOnOff[iCh - 1]) { - - if (mask & ParX) - fConstraintX.values[i * fgNParCh + 0] = 1.0; - if (mask & ParY) - fConstraintY.values[i * fgNParCh + 1] = 1.0; - if (mask & ParTZ) - fConstraintTZ.values[i * fgNParCh + 2] = 1.0; - if (mask & ParZ) - fConstraintTZ.values[i * fgNParCh + 3] = 1.0; - } - } - - if (mask & ParX) - AddConstraint(fConstraintX.values, 0.0); - if (mask & ParY) - AddConstraint(fConstraintY.values, 0.0); - if (mask & ParTZ) - AddConstraint(fConstraintTZ.values, 0.0); - if (mask & ParZ) - AddConstraint(fConstraintZ.values, 0.0); -} - -//______________________________________________________________________ -void Alignment::AddConstraints(const Bool_t* lChOnOff, const Bool_t* lVarXYT, UInt_t sidesMask) -{ - /* - questions: - - is there not redundancy/inconsistency between lDetTLBR and lSpecLROnOff ? shouldn't we use only lDetTLBR ? - - why is weight ignored for ConstrainT and ConstrainB - - why is there no constrain on z - */ - - /// Add constraint equations for selected chambers, degrees of freedom and detector half - Double_t lMeanY = 0.; - Double_t lSigmaY = 0.; - Double_t lMeanZ = 0.; - Double_t lSigmaZ = 0.; - Int_t lNDetElem = 0; - - for (Int_t i = 0; i < fgNDetElem; ++i) { - - // get chamber matching detector - const Int_t iCh(GetChamberId(i)); - - // skip detector if chamber is off - if (lChOnOff[iCh - 1]) - continue; - - // get detector element id from detector element number - const Int_t lDetElemNumber = i - fgSNDetElemCh[iCh - 1]; - const Int_t lDetElemId = iCh * 100 + lDetElemNumber; - - // skip detector if its side is off - // stations 1 and 2 - if (iCh >= 1 && iCh <= 4) { - if (lDetElemNumber == 0 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber == 1 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber == 2 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber == 3 && !(sidesMask & SideBottomRight)) - continue; - } - - // station 3 - if (iCh >= 5 && iCh <= 6) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 4 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber >= 5 && lDetElemNumber <= 10 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber >= 11 && lDetElemNumber <= 13 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber >= 14 && lDetElemNumber <= 17 && !(sidesMask & SideBottomRight)) - continue; - } - - // stations 4 and 5 - if (iCh >= 7 && iCh <= 10) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 6 && !(sidesMask & SideTopRight)) - continue; - if (lDetElemNumber >= 7 && lDetElemNumber <= 13 && !(sidesMask & SideTopLeft)) - continue; - if (lDetElemNumber >= 14 && lDetElemNumber <= 19 && !(sidesMask & SideBottomLeft)) - continue; - if (lDetElemNumber >= 20 && lDetElemNumber <= 25 && !(sidesMask & SideBottomRight)) - continue; - } - - // get global x, y and z position - Double_t lDetElemGloX = 0.; - Double_t lDetElemGloY = 0.; - Double_t lDetElemGloZ = 0.; - - auto fTransform = fTransformCreator(lDetElemId); - o2::math_utils::Point3D SlatPos{0.0, 0.0, 0.0}; - o2::math_utils::Point3D GlobalPos; - - fTransform.LocalToMaster(SlatPos, GlobalPos); - lDetElemGloX = GlobalPos.x(); - lDetElemGloY = GlobalPos.y(); - lDetElemGloZ = GlobalPos.z(); - // fTransform->Local2Global(lDetElemId, 0, 0, 0, lDetElemGloX, lDetElemGloY, lDetElemGloZ); - - // increment mean Y, mean Z, sigmas and number of accepted detectors - lMeanY += lDetElemGloY; - lSigmaY += lDetElemGloY * lDetElemGloY; - lMeanZ += lDetElemGloZ; - lSigmaZ += lDetElemGloZ * lDetElemGloZ; - lNDetElem++; - } - - // calculate mean values - lMeanY /= lNDetElem; - lSigmaY /= lNDetElem; - lSigmaY = TMath::Sqrt(lSigmaY - lMeanY * lMeanY); - lMeanZ /= lNDetElem; - lSigmaZ /= lNDetElem; - lSigmaZ = TMath::Sqrt(lSigmaZ - lMeanZ * lMeanZ); - LOG(info) << "Used " << lNDetElem << " DetElem, MeanZ= " << lMeanZ << ", SigmaZ= " << lSigmaZ; - - // create all possible arrays - Array fConstraintX[4]; // Array for constraint equation X - Array fConstraintY[4]; // Array for constraint equation Y - Array fConstraintP[4]; // Array for constraint equation P - Array fConstraintXZ[4]; // Array for constraint equation X vs Z - Array fConstraintYZ[4]; // Array for constraint equation Y vs Z - Array fConstraintPZ[4]; // Array for constraint equation P vs Z - - // do we really need these ? - Array fConstraintXY[4]; // Array for constraint equation X vs Y - Array fConstraintYY[4]; // Array for constraint equation Y vs Y - Array fConstraintPY[4]; // Array for constraint equation P vs Y - - // fill Bool_t sides array based on masks, for convenience - Bool_t lDetTLBR[4]; - lDetTLBR[0] = sidesMask & SideTop; - lDetTLBR[1] = sidesMask & SideLeft; - lDetTLBR[2] = sidesMask & SideBottom; - lDetTLBR[3] = sidesMask & SideRight; - - for (Int_t i = 0; i < fgNDetElem; ++i) { - - // get chamber matching detector - const Int_t iCh(GetChamberId(i)); - - // skip detector if chamber is off - if (!lChOnOff[iCh - 1]) - continue; - - // get detector element id from detector element number - const Int_t lDetElemNumber = i - fgSNDetElemCh[iCh - 1]; - const Int_t lDetElemId = iCh * 100 + lDetElemNumber; - - // get global x, y and z position - Double_t lDetElemGloX = 0.; - Double_t lDetElemGloY = 0.; - Double_t lDetElemGloZ = 0.; - - auto fTransform = fTransformCreator(lDetElemId); - o2::math_utils::Point3D SlatPos{0.0, 0.0, 0.0}; - o2::math_utils::Point3D GlobalPos; - - fTransform.LocalToMaster(SlatPos, GlobalPos); - lDetElemGloX = GlobalPos.x(); - lDetElemGloY = GlobalPos.y(); - lDetElemGloZ = GlobalPos.z(); - // fTransform->Local2Global(lDetElemId, 0, 0, 0, lDetElemGloX, lDetElemGloY, lDetElemGloZ); - - // loop over sides - for (Int_t iSide = 0; iSide < 4; iSide++) { - - // skip if side is not selected - if (!lDetTLBR[iSide]) - continue; - - // skip detector if it is not in the selected side - // stations 1 and 2 - if (iCh >= 1 && iCh <= 4) { - if (lDetElemNumber == 0 && !(iSide == 0 || iSide == 3)) - continue; // top-right - if (lDetElemNumber == 1 && !(iSide == 0 || iSide == 1)) - continue; // top-left - if (lDetElemNumber == 2 && !(iSide == 2 || iSide == 1)) - continue; // bottom-left - if (lDetElemNumber == 3 && !(iSide == 2 || iSide == 3)) - continue; // bottom-right - } - - // station 3 - if (iCh >= 5 && iCh <= 6) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 4 && !(iSide == 0 || iSide == 3)) - continue; // top-right - if (lDetElemNumber >= 5 && lDetElemNumber <= 9 && !(iSide == 0 || iSide == 1)) - continue; // top-left - if (lDetElemNumber >= 10 && lDetElemNumber <= 13 && !(iSide == 2 || iSide == 1)) - continue; // bottom-left - if (lDetElemNumber >= 14 && lDetElemNumber <= 17 && !(iSide == 2 || iSide == 3)) - continue; // bottom-right - } - - // stations 4 and 5 - if (iCh >= 7 && iCh <= 10) { - if (lDetElemNumber >= 0 && lDetElemNumber <= 6 && !(iSide == 0 || iSide == 3)) - continue; // top-right - if (lDetElemNumber >= 7 && lDetElemNumber <= 13 && !(iSide == 0 || iSide == 1)) - continue; // top-left - if (lDetElemNumber >= 14 && lDetElemNumber <= 19 && !(iSide == 2 || iSide == 1)) - continue; // bottom-left - if (lDetElemNumber >= 20 && lDetElemNumber <= 25 && !(iSide == 2 || iSide == 3)) - continue; // bottom-right - } - - // constrain x - if (lVarXYT[0]) - fConstraintX[iSide].values[i * fgNParCh + 0] = 1; - - // constrain y - if (lVarXYT[1]) - fConstraintY[iSide].values[i * fgNParCh + 1] = 1; - - // constrain phi (rotation around z) - if (lVarXYT[2]) - fConstraintP[iSide].values[i * fgNParCh + 2] = 1; - - // x-z shearing - if (lVarXYT[3]) - fConstraintXZ[iSide].values[i * fgNParCh + 0] = (lDetElemGloZ - lMeanZ) / lSigmaZ; - - // y-z shearing - if (lVarXYT[4]) - fConstraintYZ[iSide].values[i * fgNParCh + 1] = (lDetElemGloZ - lMeanZ) / lSigmaZ; - - // phi-z shearing - if (lVarXYT[5]) - fConstraintPZ[iSide].values[i * fgNParCh + 2] = (lDetElemGloZ - lMeanZ) / lSigmaZ; - - // x-y shearing - if (lVarXYT[6]) - fConstraintXY[iSide].values[i * fgNParCh + 0] = (lDetElemGloY - lMeanY) / lSigmaY; - - // y-y shearing - if (lVarXYT[7]) - fConstraintYY[iSide].values[i * fgNParCh + 1] = (lDetElemGloY - lMeanY) / lSigmaY; - - // phi-y shearing - if (lVarXYT[8]) - fConstraintPY[iSide].values[i * fgNParCh + 2] = (lDetElemGloY - lMeanY) / lSigmaY; - } - } - - // pass constraints to millepede - for (Int_t iSide = 0; iSide < 4; iSide++) { - // skip if side is not selected - if (!lDetTLBR[iSide]) - continue; - - if (lVarXYT[0]) - AddConstraint(fConstraintX[iSide].values, 0.0); - if (lVarXYT[1]) - AddConstraint(fConstraintY[iSide].values, 0.0); - if (lVarXYT[2]) - AddConstraint(fConstraintP[iSide].values, 0.0); - if (lVarXYT[3]) - AddConstraint(fConstraintXZ[iSide].values, 0.0); - if (lVarXYT[4]) - AddConstraint(fConstraintYZ[iSide].values, 0.0); - if (lVarXYT[5]) - AddConstraint(fConstraintPZ[iSide].values, 0.0); - if (lVarXYT[6]) - AddConstraint(fConstraintXY[iSide].values, 0.0); - if (lVarXYT[7]) - AddConstraint(fConstraintYY[iSide].values, 0.0); - if (lVarXYT[8]) - AddConstraint(fConstraintPY[iSide].values, 0.0); - } -} - -//______________________________________________________________________ -void Alignment::InitGlobalParameters(Double_t* par) -{ - /// Initialize global parameters with par array - if (!fInitialized) { - LOG(fatal) << "Millepede is not initialized"; - } - - fMillepede->SetGlobalParameters(par); -} - -//______________________________________________________________________ -void Alignment::SetAllowedVariation(Int_t iPar, Double_t value) -{ - /// "Encouraged" variation for degrees of freedom - // check initialization - if (fInitialized) { - LOG(fatal) << "Millepede already initialized"; - } - - // check initialization - if (!(iPar >= 0 && iPar < fgNParCh)) { - LOG(fatal) << "Invalid index: " << iPar; - } - - fAllowVar[iPar] = value; -} - -//______________________________________________________________________ -void Alignment::SetSigmaXY(Double_t sigmaX, Double_t sigmaY) -{ - - /// Set expected measurement resolution - fSigma[0] = sigmaX; - fSigma[1] = sigmaY; - - // print - for (Int_t i = 0; i < 2; ++i) { - LOG(info) << "fSigma[" << i << "] =" << fSigma[i]; - } -} - -//_____________________________________________________ -void Alignment::GlobalFit(Double_t* parameters, Double_t* errors, Double_t* pulls) -{ - - /// Call global fit; Global parameters are stored in parameters - fMillepede->GlobalFit(parameters, errors, pulls); - - LOG(info) << "Done fitting global parameters"; - for (int iDet = 0; iDet < fgNDetElem; ++iDet) { - LOG(info) << iDet << " " << parameters[iDet * fgNParCh + 0] << " " << parameters[iDet * fgNParCh + 1] << " " << parameters[iDet * fgNParCh + 3] << " " << parameters[iDet * fgNParCh + 2]; - } -} - -//_____________________________________________________ -void Alignment::PrintGlobalParameters() const -{ - fMillepede->PrintGlobalParameters(); -} - -//_____________________________________________________ -Double_t Alignment::GetParError(Int_t iPar) const -{ - return fMillepede->GetParError(iPar); -} - -// //______________________________________________________________________ -// AliMUONGeometryTransformer* Alignment::ReAlign( -// const AliMUONGeometryTransformer* transformer, -// const double* misAlignments, Bool_t) -// { - -// /// Returns a new AliMUONGeometryTransformer with the found misalignments -// /// applied. - -// // Takes the internal geometry module transformers, copies them -// // and gets the Detection Elements from them. -// // Takes misalignment parameters and applies these -// // to the local transform of the Detection Element -// // Obtains the global transform by multiplying the module transformer -// // transformation with the local transformation -// // Applies the global transform to a new detection element -// // Adds the new detection element to a new module transformer -// // Adds the new module transformer to a new geometry transformer -// // Returns the new geometry transformer - -// Double_t lModuleMisAlignment[fgNParCh] = {0}; -// Double_t lDetElemMisAlignment[fgNParCh] = {0}; -// const TClonesArray* oldMisAlignArray(transformer->GetMisAlignmentData()); - -// AliMUONGeometryTransformer* newGeometryTransformer = new AliMUONGeometryTransformer(); -// for (Int_t iMt = 0; iMt < transformer->GetNofModuleTransformers(); ++iMt) { - -// // module transformers -// const AliMUONGeometryModuleTransformer* kModuleTransformer = transformer->GetModuleTransformer(iMt, kTRUE); - -// AliMUONGeometryModuleTransformer* newModuleTransformer = new AliMUONGeometryModuleTransformer(iMt); -// newGeometryTransformer->AddModuleTransformer(newModuleTransformer); - -// // get transformation -// TGeoHMatrix deltaModuleTransform(DeltaTransform(lModuleMisAlignment)); - -// // update module -// TGeoHMatrix moduleTransform(*kModuleTransformer->GetTransformation()); -// TGeoHMatrix newModuleTransform(AliMUONGeometryBuilder::Multiply(deltaModuleTransform, moduleTransform)); -// newModuleTransformer->SetTransformation(newModuleTransform); - -// // Get matching old alignment and update current matrix accordingly -// if (oldMisAlignArray) { - -// const AliAlignObjMatrix* oldAlignObj(0); -// const Int_t moduleId(kModuleTransformer->GetModuleId()); -// const Int_t volId = AliGeomManager::LayerToVolUID(AliGeomManager::kMUON, moduleId); -// for (Int_t pos = 0; pos < oldMisAlignArray->GetEntriesFast(); ++pos) { - -// const AliAlignObjMatrix* localAlignObj(dynamic_cast(oldMisAlignArray->At(pos))); -// if (localAlignObj && localAlignObj->GetVolUID() == volId) { -// oldAlignObj = localAlignObj; -// break; -// } -// } - -// // multiply -// if (oldAlignObj) { - -// TGeoHMatrix oldMatrix; -// oldAlignObj->GetMatrix(oldMatrix); -// deltaModuleTransform.Multiply(&oldMatrix); -// } -// } - -// // Create module mis alignment matrix -// newGeometryTransformer->AddMisAlignModule(kModuleTransformer->GetModuleId(), deltaModuleTransform); - -// AliMpExMap* detElements = kModuleTransformer->GetDetElementStore(); - -// TIter next(detElements->CreateIterator()); -// AliMUONGeometryDetElement* detElement; -// Int_t iDe(-1); -// while ((detElement = static_cast(next()))) { -// ++iDe; -// // make a new detection element -// AliMUONGeometryDetElement* newDetElement = new AliMUONGeometryDetElement(detElement->GetId(), detElement->GetVolumePath()); -// TString lDetElemName(detElement->GetDEName()); -// lDetElemName.ReplaceAll("DE", ""); - -// // store detector element id and number -// const Int_t iDetElemId = lDetElemName.Atoi(); -// if (DetElemIsValid(iDetElemId)) { - -// const Int_t iDetElemNumber(GetDetElemNumber(iDetElemId)); - -// for (int i = 0; i < fgNParCh; ++i) { -// lDetElemMisAlignment[i] = 0.0; -// if (iMt < fgNTrkMod) { -// lDetElemMisAlignment[i] = misAlignments[iDetElemNumber * fgNParCh + i]; -// } -// } - -// // get transformation -// TGeoHMatrix deltaGlobalTransform(DeltaTransform(lDetElemMisAlignment)); - -// // update module -// TGeoHMatrix globalTransform(*detElement->GetGlobalTransformation()); -// TGeoHMatrix newGlobalTransform(AliMUONGeometryBuilder::Multiply(deltaGlobalTransform, globalTransform)); -// newDetElement->SetGlobalTransformation(newGlobalTransform); -// newModuleTransformer->GetDetElementStore()->Add(newDetElement->GetId(), newDetElement); - -// // Get matching old alignment and update current matrix accordingly -// if (oldMisAlignArray) { - -// const AliAlignObjMatrix* oldAlignObj(0); -// const int detElemId(detElement->GetId()); -// const Int_t volId = AliGeomManager::LayerToVolUID(AliGeomManager::kMUON, detElemId); -// for (Int_t pos = 0; pos < oldMisAlignArray->GetEntriesFast(); ++pos) { - -// const AliAlignObjMatrix* localAlignObj(dynamic_cast(oldMisAlignArray->At(pos))); -// if (localAlignObj && localAlignObj->GetVolUID() == volId) { -// oldAlignObj = localAlignObj; -// break; -// } -// } - -// // multiply -// if (oldAlignObj) { - -// TGeoHMatrix oldMatrix; -// oldAlignObj->GetMatrix(oldMatrix); -// deltaGlobalTransform.Multiply(&oldMatrix); -// } -// } - -// // Create misalignment matrix -// newGeometryTransformer->AddMisAlignDetElement(detElement->GetId(), deltaGlobalTransform); - -// } else { - -// // "invalid" detector elements come from MTR and are left unchanged -// Aliinfo(Form("Keeping detElement %i unchanged", iDetElemId)); - -// // update module -// TGeoHMatrix globalTransform(*detElement->GetGlobalTransformation()); -// newDetElement->SetGlobalTransformation(globalTransform); -// newModuleTransformer->GetDetElementStore()->Add(newDetElement->GetId(), newDetElement); - -// // Get matching old alignment and update current matrix accordingly -// if (oldMisAlignArray) { - -// const AliAlignObjMatrix* oldAlignObj(0); -// const int detElemId(detElement->GetId()); -// const Int_t volId = AliGeomManager::LayerToVolUID(AliGeomManager::kMUON, detElemId); -// for (Int_t pos = 0; pos < oldMisAlignArray->GetEntriesFast(); ++pos) { - -// const AliAlignObjMatrix* localAlignObj(dynamic_cast(oldMisAlignArray->At(pos))); -// if (localAlignObj && localAlignObj->GetVolUID() == volId) { -// oldAlignObj = localAlignObj; -// break; -// } -// } - -// // multiply -// if (oldAlignObj) { - -// TGeoHMatrix oldMatrix; -// oldAlignObj->GetMatrix(oldMatrix); -// newGeometryTransformer->AddMisAlignDetElement(detElement->GetId(), oldMatrix); -// } -// } -// } -// } - -// newGeometryTransformer->AddModuleTransformer(newModuleTransformer); -// } - -// return newGeometryTransformer; -// } - -//______________________________________________________________________ -void Alignment::SetAlignmentResolution(const TClonesArray* misAlignArray, Int_t rChId, Double_t chResX, Double_t chResY, Double_t deResX, Double_t deResY) -{ - - /// Set alignment resolution to misalign objects to be stored in CDB - /// if rChId is > 0 set parameters for this chamber only, counting from 1 - TMatrixDSym mChCorrMatrix(6); - mChCorrMatrix[0][0] = chResX * chResX; - mChCorrMatrix[1][1] = chResY * chResY; - - TMatrixDSym mDECorrMatrix(6); - mDECorrMatrix[0][0] = deResX * deResX; - mDECorrMatrix[1][1] = deResY * deResY; - - o2::detectors::AlignParam* alignMat = 0x0; - - for (Int_t chId = 0; chId <= 9; ++chId) { - - // skip chamber if selection is valid, and does not match - if (rChId > 0 && chId + 1 != rChId) - continue; - - TString chName1; - TString chName2; - if (chId < 4) { - - chName1 = Form("GM%d", chId); - chName2 = Form("GM%d", chId); - - } else { - - chName1 = Form("GM%d", 4 + (chId - 4) * 2); - chName2 = Form("GM%d", 4 + (chId - 4) * 2 + 1); - } - - for (int i = 0; i < misAlignArray->GetEntries(); ++i) { - - alignMat = (o2::detectors::AlignParam*)misAlignArray->At(i); - TString volName(alignMat->getSymName()); - if ((volName.Contains(chName1) && - ((volName.Last('/') == volName.Index(chName1) + chName1.Length()) || - (volName.Length() == volName.Index(chName1) + chName1.Length()))) || - (volName.Contains(chName2) && - ((volName.Last('/') == volName.Index(chName2) + chName2.Length()) || - (volName.Length() == volName.Index(chName2) + chName2.Length())))) { - - volName.Remove(0, volName.Last('/') + 1); - // if (volName.Contains("GM")){ - // alignMat->SetCorrMatrix(mChCorrMatrix); - // }else if (volName.Contains("DE")){ - // alignMat->SetCorrMatrix(mDECorrMatrix); - // } - } - } - } -} - -//_____________________________________________________ -LocalTrackParam Alignment::RefitStraightTrack(Track& track, Double_t z0) const -{ - - // initialize matrices - TMatrixD AtGASum(4, 4); - AtGASum.Zero(); - - TMatrixD AtGMSum(4, 1); - AtGMSum.Zero(); - - // loop over clusters - for (auto itTrackParam(track.begin()); itTrackParam != track.end(); ++itTrackParam) { - - // get track parameters - if (!&*itTrackParam) - continue; - - // get cluster - const Cluster* cluster = itTrackParam->getClusterPtr(); - if (!cluster) - continue; - - // projection matrix - TMatrixD A(2, 4); - A.Zero(); - A(0, 0) = 1; - A(0, 2) = (cluster->getZ() - z0); - A(1, 1) = 1; - A(1, 3) = (cluster->getZ() - z0); - - TMatrixD At(TMatrixD::kTransposed, A); - - // gain matrix - TMatrixD G(2, 2); - G.Zero(); - G(0, 0) = 1.0 / Square(cluster->getEx()); - G(1, 1) = 1.0 / Square(cluster->getEy()); - - const TMatrixD AtG(At, TMatrixD::kMult, G); - const TMatrixD AtGA(AtG, TMatrixD::kMult, A); - AtGASum += AtGA; - - // measurement - TMatrixD M(2, 1); - M(0, 0) = cluster->getX(); - M(1, 0) = cluster->getY(); - const TMatrixD AtGM(AtG, TMatrixD::kMult, M); - AtGMSum += AtGM; - } - - // perform inversion - TMatrixD AtGASumInv(TMatrixD::kInverted, AtGASum); - TMatrixD X(AtGASumInv, TMatrixD::kMult, AtGMSum); - - // // TODO: compare with initial track parameters - // Aliinfo( Form( "x: %.3f vs %.3f", fTrackPos0[0], X(0,0) ) ); - // Aliinfo( Form( "y: %.3f vs %.3f", fTrackPos0[1], X(1,0) ) ); - // Aliinfo( Form( "dxdz: %.6g vs %.6g", fTrackSlope0[0], X(2,0) ) ); - // Aliinfo( Form( "dydz: %.6g vs %.6g\n", fTrackSlope0[1], X(3,0) ) ); - - // fill output parameters - LocalTrackParam out; - out.fTrackX = X(0, 0); - out.fTrackY = X(1, 0); - out.fTrackZ = z0; - out.fTrackSlopeX = X(2, 0); - out.fTrackSlopeY = X(3, 0); - - return out; -} - -//_____________________________________________________ -void Alignment::FillDetElemData(const Cluster* cluster) -{ - // LOG(fatal) << __PRETTY_FUNCTION__ << " is disabled"; - LOG(info) << __PRETTY_FUNCTION__ << " is enabled"; - - /// Get information of current detection element - // get detector element number from Alice ID - const Int_t detElemId = cluster->getDEId(); - fDetElemNumber = GetDetElemNumber(detElemId); - - // get detector element - // const AliMUONGeometryDetElement detElement(detElemId); - auto fTransform = fTransformCreator(detElemId); - /* - get the global transformation matrix and store its inverse, in order to manually perform - the global to Local transformations needed to calculate the derivatives - */ - // fTransform = fTransform.Inverse(); - // fTransform.GetTransformMatrix(fGeoCombiTransInverse); -} - -//______________________________________________________________________ -void Alignment::FillRecPointData(const Cluster* cluster) -{ - - /// Get information of current cluster - fClustPos[0] = cluster->getX(); - fClustPos[1] = cluster->getY(); - fClustPos[2] = cluster->getZ(); -} - -//______________________________________________________________________ -void Alignment::FillTrackParamData(const TrackParam* trackParam) -{ - - /// Get information of current track at current cluster - fTrackPos[0] = trackParam->getNonBendingCoor(); - fTrackPos[1] = trackParam->getBendingCoor(); - fTrackPos[2] = trackParam->getZ(); - fTrackSlope[0] = trackParam->getNonBendingSlope(); - fTrackSlope[1] = trackParam->getBendingSlope(); -} - -//______________________________________________________________________ -void Alignment::LocalEquationX(void) -{ - /// local equation along X - - // 'inverse' (GlobalToLocal) rotation matrix - const Double_t* r(fGeoCombiTransInverse.GetRotationMatrix()); - - // local derivatives - SetLocalDerivative(0, r[0]); - SetLocalDerivative(1, r[0] * (fTrackPos[2] - fTrackPos0[2])); - SetLocalDerivative(2, r[1]); - SetLocalDerivative(3, r[1] * (fTrackPos[2] - fTrackPos0[2])); - - // global derivatives - /* - alignment parameters are - 0: delta_x - 1: delta_y - 2: delta_phiz - 3: delta_z - */ - - SetGlobalDerivative(fDetElemNumber * fgNParCh + 0, -r[0]); - SetGlobalDerivative(fDetElemNumber * fgNParCh + 1, -r[1]); - - if (fBFieldOn) { - - // use local position for derivatives vs 'delta_phi_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 2, -r[1] * fTrackPos[0] + r[0] * fTrackPos[1]); - - // use local slopes for derivatives vs 'delta_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 3, r[0] * fTrackSlope[0] + r[1] * fTrackSlope[1]); - - } else { - - // local copy of extrapolated track positions - const Double_t trackPosX = fTrackPos0[0] + fTrackSlope0[0] * (fTrackPos[2] - fTrackPos0[2]); - const Double_t trackPosY = fTrackPos0[1] + fTrackSlope0[1] * (fTrackPos[2] - fTrackPos0[2]); - - // use properly extrapolated position for derivatives vs 'delta_phi_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 2, -r[1] * trackPosX + r[0] * trackPosY); - - // use slopes at origin for derivatives vs 'delta_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 3, r[0] * fTrackSlope0[0] + r[1] * fTrackSlope0[1]); - } - - // store local equation - fMillepede->SetLocalEquation(fGlobalDerivatives, fLocalDerivatives, fMeas[0], fSigma[0]); -} - -//______________________________________________________________________ -void Alignment::LocalEquationY(void) -{ - /// local equation along Y - - // 'inverse' (GlobalToLocal) rotation matrix - const Double_t* r(fGeoCombiTransInverse.GetRotationMatrix()); - - // store local derivatives - SetLocalDerivative(0, r[3]); - SetLocalDerivative(1, r[3] * (fTrackPos[2] - fTrackPos0[2])); - SetLocalDerivative(2, r[4]); - SetLocalDerivative(3, r[4] * (fTrackPos[2] - fTrackPos0[2])); - - // set global derivatives - SetGlobalDerivative(fDetElemNumber * fgNParCh + 0, -r[3]); - SetGlobalDerivative(fDetElemNumber * fgNParCh + 1, -r[4]); - - if (fBFieldOn) { - - // use local position for derivatives vs 'delta_phi' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 2, -r[4] * fTrackPos[0] + r[3] * fTrackPos[1]); - - // use local slopes for derivatives vs 'delta_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 3, r[3] * fTrackSlope[0] + r[4] * fTrackSlope[1]); - - } else { - - // local copy of extrapolated track positions - const Double_t trackPosX = fTrackPos0[0] + fTrackSlope0[0] * (fTrackPos[2] - fTrackPos0[2]); - const Double_t trackPosY = fTrackPos0[1] + fTrackSlope0[1] * (fTrackPos[2] - fTrackPos0[2]); - - // use properly extrapolated position for derivatives vs 'delta_phi' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 2, -r[4] * trackPosX + r[3] * trackPosY); - - // use slopes at origin for derivatives vs 'delta_z' - SetGlobalDerivative(fDetElemNumber * fgNParCh + 3, r[3] * fTrackSlope0[0] + r[4] * fTrackSlope0[1]); - } - - // store local equation - fMillepede->SetLocalEquation(fGlobalDerivatives, fLocalDerivatives, fMeas[1], fSigma[1]); -} - -//_________________________________________________________________________ -TGeoCombiTrans Alignment::DeltaTransform(const double* lMisAlignment) const -{ - /// Get Delta Transformation, based on alignment parameters - - // translation - const TGeoTranslation deltaTrans(lMisAlignment[0], lMisAlignment[1], lMisAlignment[3]); - - // rotation - TGeoRotation deltaRot; - deltaRot.RotateZ(lMisAlignment[2] * 180. / TMath::Pi()); - - // combined rotation and translation. - return TGeoCombiTrans(deltaTrans, deltaRot); -} - -//______________________________________________________________________ -void Alignment::AddConstraint(Double_t* par, Double_t value) -{ - /// Constrain equation defined by par to value - if (!fInitialized) { - LOG(fatal) << "Millepede is not initialized"; - } - - fMillepede->SetGlobalConstraint(par, value); -} - -//______________________________________________________________________ -Bool_t Alignment::DetElemIsValid(Int_t iDetElemId) const -{ - /// return true if given detector element is valid (and belongs to muon tracker) - const Int_t iCh = iDetElemId / 100; - const Int_t iDet = iDetElemId % 100; - return (iCh > 0 && iCh <= fgNCh && iDet < fgNDetElemCh[iCh - 1]); -} - -//______________________________________________________________________ -Int_t Alignment::GetDetElemNumber(Int_t iDetElemId) const -{ - /// get det element number from ID - // get chamber and element number in chamber - const Int_t iCh = iDetElemId / 100; - const Int_t iDet = iDetElemId % 100; - - // make sure detector index is valid - if (!(iCh > 0 && iCh <= fgNCh && iDet < fgNDetElemCh[iCh - 1])) { - LOG(fatal) << "Invalid detector element id: " << iDetElemId; - } - - // add number of detectors up to this chamber - return iDet + fgSNDetElemCh[iCh - 1]; -} - -//______________________________________________________________________ -Int_t Alignment::GetChamberId(Int_t iDetElemNumber) const -{ - /// get chamber (counting from 1) matching a given detector element id - Int_t iCh(0); - for (iCh = 0; iCh < fgNCh; iCh++) { - if (iDetElemNumber < fgSNDetElemCh[iCh]) - break; - } - - return iCh; -} - -//______________________________________________________________________ -TString Alignment::GetParameterMaskString(UInt_t mask) const -{ - TString out; - if (mask & ParX) - out += "X"; - if (mask & ParY) - out += "Y"; - if (mask & ParZ) - out += "Z"; - if (mask & ParTZ) - out += "T"; - return out; -} - -//______________________________________________________________________ -TString Alignment::GetSidesMaskString(UInt_t mask) const -{ - TString out; - if (mask & SideTop) - out += "T"; - if (mask & SideLeft) - out += "L"; - if (mask & SideBottom) - out += "B"; - if (mask & SideRight) - out += "R"; - return out; -} - -} // namespace mch -} // namespace o2 \ No newline at end of file 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/calibration/src/NoiseSlotCalibrator.cxx b/Detectors/ITSMFT/MFT/calibration/src/NoiseSlotCalibrator.cxx deleted file mode 100644 index 13d6f3b3f567b..0000000000000 --- a/Detectors/ITSMFT/MFT/calibration/src/NoiseSlotCalibrator.cxx +++ /dev/null @@ -1,145 +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 NoiseSlotCalibrator.cxx - -#include "MFTCalibration/NoiseSlotCalibrator.h" - -#include -#include "TFile.h" -#include "DataFormatsITSMFT/Digit.h" -#include "DataFormatsITSMFT/ClusterPattern.h" -#include "DataFormatsITSMFT/ROFRecord.h" - -namespace o2 -{ -using Slot = calibration::TimeSlot; - -namespace mft -{ -bool NoiseSlotCalibrator::processTimeFrame(calibration::TFType nTF, - gsl::span const& digits, - gsl::span const& rofs) -{ - LOG(detail) << "Processing TF# " << nTF; - - auto& slotTF = getSlotForTF(nTF); - auto& noiseMap = *(slotTF.getContainer()); - - for (const auto& rof : rofs) { - auto digitsInFrame = rof.getROFData(digits); - for (const auto& d : digitsInFrame) { - auto id = d.getChipIndex(); - auto row = d.getRow(); - auto col = d.getColumn(); - - noiseMap.increaseNoiseCount(id, row, col); - } - } - noiseMap.addStrobes(rofs.size()); - mNumberOfStrobes += rofs.size(); - return hasEnoughData(slotTF); -} - -bool NoiseSlotCalibrator::processTimeFrame(calibration::TFType nTF, - gsl::span const& clusters, - gsl::span const& patterns, - gsl::span const& rofs) -{ - LOG(detail) << "Processing TF# " << nTF; - - auto& slotTF = getSlotForTF(nTF); - auto& noiseMap = *(slotTF.getContainer()); - - auto pattIt = patterns.begin(); - for (const auto& rof : rofs) { - auto clustersInFrame = rof.getROFData(clusters); - for (const auto& c : clustersInFrame) { - if (c.getPatternID() != o2::itsmft::CompCluster::InvalidPatternID) { - // For the noise calibration, we use "pass1" clusters... - continue; - } - o2::itsmft::ClusterPattern patt(pattIt); - - auto id = c.getSensorID(); - auto row = c.getRow(); - auto col = c.getCol(); - auto colSpan = patt.getColumnSpan(); - auto rowSpan = patt.getRowSpan(); - - // Fast 1-pixel calibration - if ((rowSpan == 1) && (colSpan == 1)) { - noiseMap.increaseNoiseCount(id, row, col); - continue; - } - - // All-pixel calibration - auto nBits = rowSpan * colSpan; - int ic = 0, ir = 0; - for (unsigned int i = 2; i < patt.getUsedBytes() + 2; i++) { - unsigned char tempChar = patt.getByte(i); - int s = 128; // 0b10000000 - while (s > 0) { - if ((tempChar & s) != 0) { - noiseMap.increaseNoiseCount(id, row + ir, col + ic); - } - ic++; - s >>= 1; - if ((ir + 1) * ic == nBits) { - break; - } - if (ic == colSpan) { - ic = 0; - ir++; - } - } - if ((ir + 1) * ic == nBits) { - break; - } - } - } - } - noiseMap.addStrobes(rofs.size()); - mNumberOfStrobes += rofs.size(); - return hasEnoughData(slotTF); -} - -// Functions overloaded from the calibration framework -bool NoiseSlotCalibrator::process(calibration::TFType tf, const gsl::span data) -{ - LOG(warning) << "Only 1-pix noise calibraton is possible !"; - return calibration::TimeSlotCalibration::process(tf, data); -} - -// Functions required by the calibration framework - -Slot& NoiseSlotCalibrator::emplaceNewSlot(bool front, calibration::TFType tstart, calibration::TFType tend) -{ - auto& cont = getSlots(); - auto& slot = front ? cont.emplace_front(tstart, tend) : cont.emplace_back(tstart, tend); - slot.setContainer(std::make_unique(936)); - return slot; -} - -bool NoiseSlotCalibrator::hasEnoughData(const Slot& slot) const -{ - return slot.getContainer()->getNumberOfStrobes() > mMinROFs ? true : false; -} - -void NoiseSlotCalibrator::finalizeSlot(Slot& slot) -{ - o2::itsmft::NoiseMap* map = slot.getContainer(); - LOG(info) << "Number of processed strobes is " << map->getNumberOfStrobes(); - map->applyProbThreshold(mProbabilityThreshold, map->getNumberOfStrobes(), mProbRelErr); -} - -} // namespace mft -} // namespace o2 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/CMakeLists.txt b/Detectors/ITSMFT/MFT/workflow/CMakeLists.txt index acb3d0b3e835f..b83699498a6b8 100644 --- a/Detectors/ITSMFT/MFT/workflow/CMakeLists.txt +++ b/Detectors/ITSMFT/MFT/workflow/CMakeLists.txt @@ -12,8 +12,6 @@ o2_add_library(MFTWorkflow TARGETVARNAME targetName SOURCES src/RecoWorkflow.cxx - src/ClustererSpec.cxx - src/ClusterWriterSpec.cxx src/TrackerSpec.cxx src/TrackReaderSpec.cxx src/TrackWriterSpec.cxx 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/ClusterWriterSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/ClusterWriterSpec.cxx deleted file mode 100644 index c8061310e34f6..0000000000000 --- a/Detectors/ITSMFT/MFT/workflow/src/ClusterWriterSpec.cxx +++ /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 ClusterWriterSpec.cxx - -#include - -#include "MFTWorkflow/ClusterWriterSpec.h" -#include "DPLUtils/MakeRootTreeWriterSpec.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "DataFormatsITSMFT/ROFRecord.h" - -using namespace o2::framework; - -namespace o2 -{ -namespace mft -{ - -template -using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; -using CompClusType = std::vector; -using PatternsType = std::vector; -using ROFrameRType = std::vector; -using LabelsType = o2::dataformats::MCTruthContainer; -using ROFRecLblT = std::vector; -using namespace o2::header; - -DataProcessorSpec getClusterWriterSpec(bool useMC) -{ - // Spectators for logging - // this is only to restore the original behavior - auto compClustersSize = std::make_shared(0); - auto compClustersSizeGetter = [compClustersSize](CompClusType const& compClusters) { - *compClustersSize = compClusters.size(); - }; - auto logger = [compClustersSize](std::vector const& rofs) { - LOG(info) << "MFTClusterWriter pulled " << *compClustersSize << " clusters, in " << rofs.size() << " RO frames"; - }; - return MakeRootTreeWriterSpec("mft-cluster-writer", - "mftclusters.root", - MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Tree with MFT clusters"}, - BranchDefinition{InputSpec{"compclus", "MFT", "COMPCLUSTERS", 0}, - "MFTClusterComp", - compClustersSizeGetter}, - BranchDefinition{InputSpec{"patterns", "MFT", "PATTERNS", 0}, - "MFTClusterPatt"}, - BranchDefinition{InputSpec{"ROframes", "MFT", "CLUSTERSROF", 0}, - "MFTClustersROF", - logger}, - BranchDefinition{InputSpec{"labels", "MFT", "CLUSTERSMCTR", 0}, - "MFTClusterMCTruth", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""}, - BranchDefinition{InputSpec{"MC2ROframes", "MFT", "CLUSTERSMC2ROF", 0}, - "MFTClustersMC2ROF", - (useMC ? 1 : 0), // one branch if mc labels enabled - ""})(); -} - -} // namespace mft -} // namespace o2 diff --git a/Detectors/ITSMFT/MFT/workflow/src/ClustererSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/ClustererSpec.cxx deleted file mode 100644 index 766d7c1a0729e..0000000000000 --- a/Detectors/ITSMFT/MFT/workflow/src/ClustererSpec.cxx +++ /dev/null @@ -1,212 +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 ClustererSpec.cxx - -#include - -#include "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "MFTWorkflow/ClustererSpec.h" -#include "DataFormatsITSMFT/Digit.h" -#include "ITSMFTReconstruction/ChipMappingMFT.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "DataFormatsITSMFT/TopologyDictionary.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/ConstMCTruthContainer.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "DataFormatsParameters/GRPObject.h" -#include "ITSMFTReconstruction/DigitPixelReader.h" -#include "DetectorsBase/GeometryManager.h" -#include "MFTBase/GeometryTGeo.h" -#include "ITSMFTBase/DPLAlpideParam.h" -#include "CommonConstants/LHCConstants.h" -#include "DetectorsCommonDataFormats/DetectorNameConf.h" -#include "ITSMFTReconstruction/ClustererParam.h" - -using namespace o2::framework; -using namespace o2::itsmft; - -namespace o2 -{ -namespace mft -{ - -void ClustererDPL::init(InitContext& ic) -{ - mClusterer = std::make_unique(); - mClusterer->setNChips(o2::itsmft::ChipMappingMFT::getNChips()); - mUseClusterDictionary = !ic.options().get("ignore-cluster-dictionary"); - o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); - mNThreads = std::max(1, ic.options().get("nthreads")); - mState = 1; -} - -void ClustererDPL::run(ProcessingContext& pc) -{ - updateTimeDependentParams(pc); - auto digits = pc.inputs().get>("digits"); - auto rofs = pc.inputs().get>("ROframes"); - - gsl::span mc2rofs; - gsl::span labelbuffer; - if (mUseMC) { - labelbuffer = pc.inputs().get>("labels"); - mc2rofs = pc.inputs().get>("MC2ROframes"); - } - const o2::dataformats::ConstMCTruthContainerView labels(labelbuffer); - - LOG(debug) << "MFTClusterer pulled " << digits.size() << " digits, in " - << rofs.size() << " RO frames"; - - o2::itsmft::DigitPixelReader reader; - reader.setSquashingDepth(mClusterer->getMaxROFDepthToSquash()); - reader.setSquashingDist(mClusterer->getMaxRowColDiffToMask()); // Sharing same parameter/logic with masking - reader.setMaxBCSeparationToSquash(mClusterer->getMaxBCSeparationToSquash()); - reader.setDigits(digits); - reader.setROFRecords(rofs); - if (mUseMC) { - reader.setMC2ROFRecords(mc2rofs); - reader.setDigitsMCTruth(labels.getIndexedSize() > 0 ? &labels : nullptr); - } - reader.init(); - auto orig = o2::header::gDataOriginMFT; - std::vector clusCompVec; - std::vector clusROFVec; - std::vector clusPattVec; - - std::unique_ptr> clusterLabels; - if (mUseMC) { - clusterLabels = std::make_unique>(); - } - mClusterer->process(mNThreads, reader, &clusCompVec, &clusPattVec, &clusROFVec, clusterLabels.get()); - pc.outputs().snapshot(Output{orig, "COMPCLUSTERS", 0}, clusCompVec); - pc.outputs().snapshot(Output{orig, "CLUSTERSROF", 0}, clusROFVec); - pc.outputs().snapshot(Output{orig, "PATTERNS", 0}, clusPattVec); - - if (mUseMC) { - pc.outputs().snapshot(Output{orig, "CLUSTERSMCTR", 0}, *clusterLabels.get()); // at the moment requires snapshot - std::vector clusterMC2ROframes(mc2rofs.size()); - for (int i = mc2rofs.size(); i--;) { - clusterMC2ROframes[i] = mc2rofs[i]; // Simply, replicate it from digits ? - } - pc.outputs().snapshot(Output{orig, "CLUSTERSMC2ROF", 0}, clusterMC2ROframes); - } - - // TODO: in principle, after masking "overflow" pixels the MC2ROFRecord maxROF supposed to change, nominally to minROF - // -> consider recalculationg maxROF - LOG(debug) << "MFTClusterer pushed " << clusCompVec.size() << " compressed clusters, in " << clusROFVec.size() << " RO frames"; -} - -///_______________________________________ -void ClustererDPL::updateTimeDependentParams(ProcessingContext& pc) -{ - o2::base::GRPGeomHelper::instance().checkUpdates(pc); - static bool initOnceDone = false; - if (!initOnceDone) { // this params need to be queried only once - initOnceDone = true; - pc.inputs().get("cldict"); // just to trigger the finaliseCCDB - pc.inputs().get*>("alppar"); - pc.inputs().get*>("cluspar"); - mClusterer->setContinuousReadOut(o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(o2::detectors::DetID::MFT)); - // settings for the fired pixel overflow masking - const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); - const auto& clParams = o2::itsmft::ClustererParam::Instance(); - if (clParams.maxBCDiffToMaskBias > 0 && clParams.maxBCDiffToSquashBias > 0) { - LOGP(fatal, "maxBCDiffToMaskBias = {} and maxBCDiffToSquashBias = {} cannot be set at the same time. Either set masking or squashing with a BCDiff > 0", clParams.maxBCDiffToMaskBias, clParams.maxBCDiffToSquashBias); - } - mClusterer->setDropHugeClusters(clParams.dropHugeClusters); - auto nbc = clParams.maxBCDiffToMaskBias; - nbc += mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); - mClusterer->setMaxBCSeparationToMask(nbc); - mClusterer->setMaxRowColDiffToMask(clParams.maxRowColDiffToMask); - // Squasher - int rofBC = mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); // ROF length in BC - mClusterer->setMaxBCSeparationToSquash(rofBC + clParams.maxBCDiffToSquashBias); - int nROFsToSquash = 0; // squashing disabled if no reset due to maxSOTMUS>0. - if (clParams.maxSOTMUS > 0 && rofBC > 0) { - nROFsToSquash = 2 + int(clParams.maxSOTMUS / (rofBC * o2::constants::lhc::LHCBunchSpacingMUS)); // use squashing - } - mClusterer->setMaxROFDepthToSquash(nROFsToSquash); - mClusterer->print(); - } - // we may have other params which need to be queried regularly -} - -///_______________________________________ -void ClustererDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) -{ - if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { - return; - } - if (matcher == ConcreteDataMatcher("MFT", "CLUSDICT", 0)) { - LOG(info) << "cluster dictionary updated" << (!mUseClusterDictionary ? " but its using is disabled" : ""); - if (mUseClusterDictionary) { - mClusterer->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("MFT", "ALPIDEPARAM", 0)) { - LOG(info) << "Alpide param updated"; - const auto& par = o2::itsmft::DPLAlpideParam::Instance(); - par.printKeyValues(); - return; - } - if (matcher == ConcreteDataMatcher("MFT", "CLUSPARAM", 0)) { - LOG(info) << "Cluster param updated"; - const auto& par = o2::itsmft::ClustererParam::Instance(); - par.printKeyValues(); - return; - } -} - -DataProcessorSpec getClustererSpec(bool useMC) -{ - std::vector inputs; - inputs.emplace_back("digits", "MFT", "DIGITS", 0, Lifetime::Timeframe); - inputs.emplace_back("ROframes", "MFT", "DIGITSROF", 0, Lifetime::Timeframe); - inputs.emplace_back("cldict", "MFT", "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec("MFT/Calib/ClusterDictionary")); - inputs.emplace_back("cluspar", "MFT", "CLUSPARAM", 0, Lifetime::Condition, ccdbParamSpec("MFT/Config/ClustererParam")); - inputs.emplace_back("alppar", "MFT", "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec("MFT/Config/AlpideParam")); - auto ggRequest = std::make_shared(false, // orbitResetTime - true, // GRPECS=true - false, // GRPLHCIF - false, // GRPMagField - false, // askMatLUT - o2::base::GRPGeomRequest::None, // geometry - inputs, - true); - std::vector outputs; - outputs.emplace_back("MFT", "COMPCLUSTERS", 0, Lifetime::Timeframe); - outputs.emplace_back("MFT", "PATTERNS", 0, Lifetime::Timeframe); - outputs.emplace_back("MFT", "CLUSTERSROF", 0, Lifetime::Timeframe); - - if (useMC) { - inputs.emplace_back("labels", "MFT", "DIGITSMCTR", 0, Lifetime::Timeframe); - inputs.emplace_back("MC2ROframes", "MFT", "DIGITSMC2ROF", 0, Lifetime::Timeframe); - outputs.emplace_back("MFT", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - outputs.emplace_back("MFT", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); - } - - return DataProcessorSpec{ - "mft-clusterer", - inputs, - outputs, - AlgorithmSpec{adaptFromTask(ggRequest, useMC)}, - 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 mft -} // namespace o2 diff --git a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx index 615c9c1b275d4..178c1dd50f4df 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/RecoWorkflow.cxx @@ -12,9 +12,9 @@ /// @file RecoWorkflow.cxx #include +#include "ITSMFTWorkflow/ClustererSpec.h" +#include "ITSMFTWorkflow/ClusterWriterSpec.h" #include "MFTWorkflow/RecoWorkflow.h" -#include "MFTWorkflow/ClustererSpec.h" -#include "MFTWorkflow/ClusterWriterSpec.h" #include "MFTWorkflow/TrackerSpec.h" #include "MFTWorkflow/TrackWriterSpec.h" #include "ITSMFTWorkflow/DigitReaderSpec.h" @@ -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::mft::getClustererSpec(useMC)); + specs.emplace_back(o2::itsmft::getMFTClustererSpec(useMC, doStag)); } - if (!disableRootOutput) { - specs.emplace_back(o2::mft::getClusterWriterSpec(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 d8e15590474ec..6ceb04b3c4df6 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx @@ -24,6 +24,7 @@ #include "TGeoGlobalMagField.h" +#include "Framework/DeviceSpec.h" #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" @@ -44,7 +45,7 @@ namespace o2 { namespace mft { -//#define _TIMING_ +// #define _TIMING_ void TrackerDPL::init(InitContext& ic) { @@ -97,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; @@ -260,7 +255,7 @@ void TrackerDPL::run(ProcessingContext& pc) rof->setFirstEntry(firstROFTrackEntry); rof->setNEntries(ntracksROF); - *rof++; + rof++; roFrameId++; } } @@ -318,17 +313,24 @@ 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; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::mft::MFTTrackingParam::Instance().getName()), o2::mft::MFTTrackingParam::Instance().getName()); + } } mTimer[SWTot].Stop(); @@ -457,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 f42b2e0c92a4a..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. // @@ -9,7 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "MFTWorkflow/ClusterWriterSpec.h" +#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::mft::getClusterWriterSpec(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/include/ITSMFTBase/GeometryTGeo.h b/Detectors/ITSMFT/common/base/include/ITSMFTBase/GeometryTGeo.h index fda38b21812ee..3051176fb23c6 100644 --- a/Detectors/ITSMFT/common/base/include/ITSMFTBase/GeometryTGeo.h +++ b/Detectors/ITSMFT/common/base/include/ITSMFTBase/GeometryTGeo.h @@ -11,7 +11,7 @@ /// \file GeometryTGeo.h /// \brief Definition of the GeometryTGeo class : common part for ITS and MFT -/// \brief collects ITS and MFT common methods to acces matrices +/// \brief collects ITS and MFT common methods to access matrices /// \author ruben.shahoyan@cern.ch #ifndef ALICEO2_ITSMFT_GEOMETRYTGEO_H_ @@ -37,7 +37,8 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache ~GeometryTGeo() override = default; Int_t getNumberOfChips() const { return mSize; } - /// build detector layout data, must be overriden by detector + virtual Int_t getLayer(Int_t index) const { return -1; }; + /// build detector layout data, must be overridden by detector virtual void Build(int loadTrans) = 0; bool isOwner() const { return mOwner; } 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/data/AlpideResponseData/CMakeLists.txt b/Detectors/ITSMFT/common/data/AlpideResponseData/CMakeLists.txt index d1f3e756394b1..f985857afa88c 100644 --- a/Detectors/ITSMFT/common/data/AlpideResponseData/CMakeLists.txt +++ b/Detectors/ITSMFT/common/data/AlpideResponseData/CMakeLists.txt @@ -9,33 +9,8 @@ # granted to it by virtue of its status as an Intergovernmental Organization # or submit itself to any jurisdiction. - o2_add_executable(alpide-response-generator SOURCES AlpideResponse.cxx PUBLIC_LINK_LIBRARIES O2::ITSMFTSimulation ROOT::Core TARGETVARNAME targetName) - -set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/Detectors/ITSMFT/common/data/AlpideResponseData/AlpideResponse.cxx) - -if(ITSRESPONSE) - message(STATUS "ITSRESPONSE option provided, setting ITSRESPONSE_DIR from it: " ${ITSRESPONSE}) - set(ITSRESPONSE_DIR ${ITSRESPONSE} CACHE PATH "ITSResponse directory") -else() - message(STATUS "ITSRESPONSE option not provided, setting ITSRESPONSE_DIR from environment ITSRESPONSE_ROOT: " $ENV{ITSRESPONSE_ROOT}) - set(ITSRESPONSE_DIR $ENV{ITSRESPONSE_ROOT} CACHE PATH "ITSResponse directory") -endif() - -add_custom_command(TARGET O2exe-alpide-response-generator POST_BUILD - COMMAND ${CMAKE_BINARY_DIR}/stage/bin/o2-alpide-response-generator -i ${ITSRESPONSE_DIR}/response/AlpideResponseData/ -o ${CMAKE_CURRENT_BINARY_DIR}/ - BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/AlpideResponseData.root - COMMENT "Generating AlpideResponseData.root" -) - -# # Add a target that depends on the custom command output -add_custom_target( - GenerateAlpideResponse ALL - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/AlpideResponseData.root -) - -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/AlpideResponseData.root" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/Detectors/ITSMFT/data/AlpideResponseData/") diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/CTFCoder.h index 2d4aabc94fc82..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 { -class CTFCoder : public o2::ctf::CTFCoderBase +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) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), det) {} + 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 : 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 960ce2ca33d5b..dd3052e2cc5bd 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/Clusterer.h @@ -121,6 +121,10 @@ class Clusterer }; struct ClustererThread { + struct PreCluster { + int head = 0; // index of precluster head in the pixels + int index = 0; + }; int id = -1; Clusterer* parent = nullptr; // parent clusterer // buffers for entries in preClusterIndices in 2 columns, to avoid boundary checks, we reserve @@ -132,12 +136,11 @@ class Clusterer // pixels[].first is the index of the next pixel of the same precluster in the pixels // pixels[].second is the index of the referred pixel in the ChipPixelData (element of mChips) std::vector> pixels; - std::vector preClusterHeads; // index of precluster head in the pixels - std::vector preClusterIndices; uint16_t currCol = 0xffff; ///< Column being processed bool noLeftCol = true; ///< flag that there is no column on the left to check std::array labelsBuff; //! temporary buffer for building cluster labels std::vector pixArrBuff; //! temporary buffer for pattern calc. + std::vector preClusters; //! preclusters info // /// temporary storage for the thread output CompClusCont compClusters; @@ -154,7 +157,7 @@ class Clusterer ///< add cluster at row (entry ip in the ChipPixeData) to the precluster with given index void expandPreCluster(uint32_t ip, uint16_t row, int preClusIndex) { - auto& firstIndex = preClusterHeads[preClusterIndices[preClusIndex]]; + auto& firstIndex = preClusters[preClusters[preClusIndex].index].head; pixels.emplace_back(firstIndex, ip); firstIndex = pixels.size() - 1; curr[row] = preClusIndex; @@ -163,11 +166,10 @@ class Clusterer ///< add new precluster at given row of current column for the fired pixel with index ip in the ChipPixelData void addNewPrecluster(uint32_t ip, uint16_t row) { - preClusterHeads.push_back(pixels.size()); + int lastIndex = preClusters.size(); + preClusters.emplace_back(pixels.size(), lastIndex); // new head does not point yet (-1) on other pixels, store just the entry of the pixel in the ChipPixelData pixels.emplace_back(-1, ip); - int lastIndex = preClusterIndices.size(); - preClusterIndices.push_back(lastIndex); curr[row] = lastIndex; // store index of the new precluster in the current column buffer } @@ -213,13 +215,15 @@ class Clusterer int getMaxRowColDiffToMask() const { return mMaxRowColDiffToMask; } void setMaxRowColDiffToMask(int v) { mMaxRowColDiffToMask = v; } - int getMaxROFDepthToSquash() const { return mSquashingDepth; } + int getMaxROFDepthToSquash(int layer = -1) const { return (layer < 0) ? mSquashingDepth : mSquashingLayerDepth[layer]; } void setMaxROFDepthToSquash(int v) { mSquashingDepth = v; } + void addMaxROFDepthToSquash(int v) { mSquashingLayerDepth.push_back(v); } - int getMaxBCSeparationToSquash() const { return mMaxBCSeparationToSquash; } + int getMaxBCSeparationToSquash(int layer = -1) const { return (layer < 0) ? mMaxBCSeparationToSquash : mMaxBCSeparationToSquashLayer[layer]; } void setMaxBCSeparationToSquash(int n) { mMaxBCSeparationToSquash = n; } + void addMaxBCSeparationToSquash(int n) { mMaxBCSeparationToSquashLayer.push_back(n); } - void print() const; + void print(bool showTiming = true) const; void clear(); void reset(); @@ -232,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 @@ -243,7 +249,7 @@ class Clusterer bool mContinuousReadout = true; ///< flag continuous readout bool mDropHugeClusters = false; ///< don't include clusters that would be split in more than one - ///< mask continuosly fired pixels in frames separated by less than this amount of BCs (fired from hit in prev. ROF) + ///< mask continuously fired pixels in frames separated by less than this amount of BCs (fired from hit in prev. ROF) int mMaxBCSeparationToMask = 6000. / o2::constants::lhc::LHCBunchSpacingNS + 10; int mMaxRowColDiffToMask = 0; ///< provide their difference in col/row is <= than this int mNHugeClus = 0; ///< number of encountered huge clusters @@ -251,6 +257,8 @@ class Clusterer ///< Squashing options int mSquashingDepth = 0; ///< squashing is applied to next N rofs int mMaxBCSeparationToSquash = 6000. / o2::constants::lhc::LHCBunchSpacingNS + 10; + std::vector mSquashingLayerDepth; + std::vector mMaxBCSeparationToSquashLayer; std::vector> mThreads; // buffers for threads std::vector mChips; // currently processed ROF's chips data @@ -286,7 +294,7 @@ void Clusterer::streamCluster(const std::vector& pixbuf, const std::a uint16_t row = bbox.rowMin, col = bbox.colMin; if (pattID == CompCluster::InvalidPatternID || pattIdConverter.isGroup(pattID)) { if (pattID != CompCluster::InvalidPatternID) { - // For groupped topologies, the reference pixel is the COG pixel + // For grouped topologies, the reference pixel is the COG pixel float xCOG = 0., zCOG = 0.; ClusterPattern::getCOG(rowSpanW, colSpanW, patt.data(), xCOG, zCOG); row += round(xCOG); diff --git a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ClustererParam.h b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ClustererParam.h index a71e5f3095b06..3188a4f3b0010 100644 --- a/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ClustererParam.h +++ b/Detectors/ITSMFT/common/reconstruction/include/ITSMFTReconstruction/ClustererParam.h @@ -29,16 +29,26 @@ template struct ClustererParam : public o2::conf::ConfigurableParamHelper> { static_assert(N == o2::detectors::DetID::ITS || N == o2::detectors::DetID::MFT, "only DetID::ITS or DetID:: MFT are allowed"); + static constexpr int getNLayers() + { + return N == o2::detectors::DetID::ITS ? 7 : 10; + } + static constexpr std::string_view getParamName() { return N == o2::detectors::DetID::ITS ? ParamName[0] : ParamName[1]; } - int maxRowColDiffToMask = DEFRowColDiffToMask(); ///< pixel may be masked as overflow if such a neighbour in prev frame was fired - int maxBCDiffToMaskBias = 10; ///< mask if 2 ROFs differ by <= StrobeLength + Bias BCs, use value <0 to disable masking - int maxBCDiffToSquashBias = -10; ///< squash if 2 ROFs differ by <= StrobeLength + Bias BCs, use value <0 to disable squashing - float maxSOTMUS = 8.; ///< max expected signal over threshold in \mus - bool dropHugeClusters = false; ///< option to drop huge clusters (mitigate beam background) + int maxRowColDiffToMask = DEFRowColDiffToMask(); ///< pixel may be masked as overflow if such a neighbour in prev frame was fired + int maxBCDiffToMaskBias = 10; ///< mask if 2 ROFs differ by <= StrobeLength + Bias BCs, use value <0 to disable masking + int maxBCDiffToSquashBias = -10; ///< squash if 2 ROFs differ by <= StrobeLength + Bias BCs, use value <0 to disable squashing + float maxSOTMUS = 8.; ///< max expected signal over threshold in \mus + bool dropHugeClusters = false; ///< option to drop huge clusters (mitigate beam background) + int maxBCDiffToSquashBiasLayer[getNLayers()] = {}; ///< squash mask per layer + int getMaxBCDiffToSquashBias(int layer) const noexcept + { + return maxBCDiffToSquashBiasLayer[layer] ? maxBCDiffToSquashBiasLayer[layer] : maxBCDiffToSquashBias; + } O2ParamDef(ClustererParam, getParamName().data()); @@ -46,7 +56,7 @@ struct ClustererParam : public o2::conf::ConfigurableParamHelper #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 15dcc67a8967b..dcc268a4504a9 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/Clusterer.cxx @@ -30,11 +30,8 @@ void Clusterer::process(int nThreads, PixelReader& reader, CompClusCont* compClu #ifdef _PERFORM_TIMING_ mTimer.Start(kFALSE); #endif - if (nThreads < 1) { - nThreads = 1; - } + nThreads = std::max(nThreads, 1); auto autoDecode = reader.getDecodeNextAuto(); - int rofcount{0}; o2::InteractionRecord lastIR{}; do { if (autoDecode) { @@ -73,9 +70,7 @@ void Clusterer::process(int nThreads, PixelReader& reader, CompClusCont* compClu } break; // just 1 ROF was asked to be processed } - if (nFired < nThreads) { - nThreads = nFired; - } + nThreads = std::min(nFired, nThreads); #ifndef WITH_OPENMP nThreads = 1; #endif @@ -133,15 +128,17 @@ void Clusterer::process(int nThreads, PixelReader& reader, CompClusCont* compClu if (stat.firstChip == chid) { thrStatIdx[ith]++; chid += stat.nChips; // next chip to look - const auto clbeg = mThreads[ith]->compClusters.begin() + stat.firstClus; - auto szold = compClus->size(); - compClus->insert(compClus->end(), clbeg, clbeg + stat.nClus); - if (patterns) { - const auto ptbeg = mThreads[ith]->patterns.begin() + stat.firstPatt; - patterns->insert(patterns->end(), ptbeg, ptbeg + stat.nPatt); - } - if (labelsCl) { - labelsCl->mergeAtBack(mThreads[ith]->labels, stat.firstClus, stat.nClus); + if (stat.nClus > 0) { + const auto clbeg = mThreads[ith]->compClusters.begin() + stat.firstClus; + auto szold = compClus->size(); + compClus->insert(compClus->end(), clbeg, clbeg + stat.nClus); + if (patterns) { + const auto ptbeg = mThreads[ith]->patterns.begin() + stat.firstPatt; + patterns->insert(patterns->end(), ptbeg, ptbeg + stat.nPatt); + } + if (labelsCl) { + labelsCl->mergeAtBack(mThreads[ith]->labels, stat.firstClus, stat.nClus); + } } } } @@ -171,7 +168,7 @@ void Clusterer::ClustererThread::process(uint16_t chip, uint16_t nChips, CompClu const ConstMCTruth* labelsDigPtr, MCTruth* labelsClPtr, const ROFRecord& rofPtr) { if (stats.empty() || stats.back().firstChip + stats.back().nChips != chip) { // there is a jump, register new block - stats.emplace_back(ThreadStat{chip, 0, uint32_t(compClusPtr->size()), patternsPtr ? uint32_t(patternsPtr->size()) : 0, 0, 0}); + stats.emplace_back(ThreadStat{.firstChip = chip, .nChips = 0, .firstClus = uint32_t(compClusPtr->size()), .firstPatt = patternsPtr ? uint32_t(patternsPtr->size()) : 0, .nClus = 0, .nPatt = 0}); } for (int ic = 0; ic < nChips; ic++) { auto* curChipData = parent->mFiredChipsPtr[chip + ic]; @@ -214,14 +211,22 @@ void Clusterer::ClustererThread::finishChip(ChipPixelData* curChipData, CompClus PatternCont* patternsPtr, const ConstMCTruth* labelsDigPtr, MCTruth* labelsClusPtr) { const auto& pixData = curChipData->getData(); - for (int i1 = 0; i1 < preClusterHeads.size(); ++i1) { - auto ci = preClusterIndices[i1]; + int nPreclusters = preClusters.size(); + // account for the eventual reindexing of preClusters: Id2 might have been reindexed to Id1, which later was reindexed to Id0 + for (int i = 1; i < nPreclusters; i++) { + if (preClusters[i].index != i) { // reindexing is always done towards smallest index + preClusters[i].index = preClusters[preClusters[i].index].index; + } + } + for (int i1 = 0; i1 < nPreclusters; ++i1) { + auto& preCluster = preClusters[i1]; + auto ci = preCluster.index; if (ci < 0) { continue; } BBox bbox(curChipData->getChipID()); int nlab = 0; - int next = preClusterHeads[i1]; + int next = preCluster.head; pixArrBuff.clear(); while (next >= 0) { const auto& pixEntry = pixels[next]; @@ -237,12 +242,13 @@ void Clusterer::ClustererThread::finishChip(ChipPixelData* curChipData, CompClus } next = pixEntry.first; } - preClusterIndices[i1] = -1; - for (int i2 = i1 + 1; i2 < preClusterHeads.size(); ++i2) { - if (preClusterIndices[i2] != ci) { + preCluster.index = -1; + for (int i2 = i1 + 1; i2 < nPreclusters; ++i2) { + auto& preCluster2 = preClusters[i2]; + if (preCluster2.index != ci) { continue; } - next = preClusterHeads[i2]; + next = preCluster2.head; while (next >= 0) { const auto& pixEntry = pixels[next]; const auto pix = pixData[pixEntry.second]; // PixelData @@ -257,7 +263,7 @@ void Clusterer::ClustererThread::finishChip(ChipPixelData* curChipData, CompClus } next = pixEntry.first; } - preClusterIndices[i2] = -1; + preCluster2.index = -1; } if (bbox.isAcceptableSize()) { parent->streamCluster(pixArrBuff, &labelsBuff, bbox, parent->mPattIdConverter, compClusPtr, patternsPtr, labelsClusPtr, nlab); @@ -344,18 +350,15 @@ void Clusterer::ClustererThread::initChip(const ChipPixelData* curChipData, uint prev = column1 + 1; curr = column2 + 1; resetColumn(curr); - pixels.clear(); - preClusterHeads.clear(); - preClusterIndices.clear(); + preClusters.clear(); auto pix = curChipData->getData()[first]; currCol = pix.getCol(); curr[pix.getRowDirect()] = 0; // can use getRowDirect since the pixel is not masked // start the first pre-cluster - preClusterHeads.push_back(0); - preClusterIndices.push_back(0); + preClusters.emplace_back(); pixels.emplace_back(-1, first); // id of current pixel - noLeftCol = true; // flag that there is no column on the left to check yet + noLeftCol = true; } //__________________________________________________ @@ -378,39 +381,58 @@ void Clusterer::ClustererThread::updateChip(const ChipPixelData* curChipData, ui currCol = pix.getCol(); } - Bool_t orphan = true; - if (noLeftCol) { // check only the row above if (curr[row - 1] >= 0) { expandPreCluster(ip, row, curr[row - 1]); // attach to the precluster of the previous row - return; + } else { + addNewPrecluster(ip, row); // start new precluster } } else { + // row above should be always checked + int nnb = 0, lowestIndex = curr[row - 1], lowestNb = 0, *nbrCol[4], nbrRow[4]; + if (lowestIndex >= 0) { + nbrCol[nnb] = curr; + nbrRow[nnb++] = row - 1; + } else { + lowestIndex = 0x7ffff; + lowestNb = -1; + } #ifdef _ALLOW_DIAGONAL_ALPIDE_CLUSTERS_ - int neighbours[]{curr[row - 1], prev[row], prev[row + 1], prev[row - 1]}; -#else - int neighbours[]{curr[row - 1], prev[row]}; -#endif - for (auto pci : neighbours) { - if (pci < 0) { - continue; + for (int i : {-1, 0, 1}) { + auto v = prev[row + i]; + if (v >= 0) { + nbrCol[nnb] = prev; + nbrRow[nnb] = row + i; + if (v < lowestIndex) { + lowestIndex = v; + lowestNb = nnb; + } + nnb++; } - if (orphan) { - expandPreCluster(ip, row, pci); // attach to the adjascent precluster - orphan = false; - continue; + } +#else + if (prev[row] >= 0) { + nbrCol[nnb] = prev; + nbrRow[nnb] = row; + if (prev[row] < lowestIndex) { + lowestIndex = v; + lowestNb = nnb; } - // reassign precluster index to smallest one - if (preClusterIndices[pci] < preClusterIndices[curr[row]]) { - preClusterIndices[curr[row]] = preClusterIndices[pci]; - } else { - preClusterIndices[pci] = preClusterIndices[curr[row]]; + nnb++; + } +#endif + if (!nnb) { // no neighbours, create new precluster + addNewPrecluster(ip, row); // start new precluster + } else { + expandPreCluster(ip, row, lowestIndex); // attach to the adjascent precluster with smallest index + if (nnb > 1) { + for (int inb = 0; inb < nnb; inb++) { // reassign precluster index to smallest one, replicating updated values to columns caches + auto& prevIndex = (nbrCol[inb])[nbrRow[inb]]; + prevIndex = preClusters[prevIndex].index = lowestIndex; + } } } } - if (orphan) { - addNewPrecluster(ip, row); // start new precluster - } } //__________________________________________________ @@ -449,22 +471,31 @@ void Clusterer::clear() } //__________________________________________________ -void Clusterer::print() const +void Clusterer::print(bool showsTiming) const { // print settings - LOGP(info, "Clusterizer squashes overflow pixels separated by {} BC and <= {} in row/col seeking down to {} neighbour ROFs", mMaxBCSeparationToSquash, mMaxRowColDiffToMask, mSquashingDepth); + if (mSquashingLayerDepth.empty()) { + LOGP(info, "Clusterizer squashes overflow pixels separated by {} BC and <= {} in row/col seeking down to {} neighbour ROFs", mMaxBCSeparationToSquash, mMaxRowColDiffToMask, mSquashingDepth); + } else { + LOGP(info, "Clusterizer squashes overflow pixels <= {} in row/col", mMaxRowColDiffToMask); + for (size_t i{0}; i < mSquashingLayerDepth.size(); ++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); LOGP(info, "Clusterizer does {} drop huge clusters", mDropHugeClusters ? "" : "not"); + if (showsTiming) { #ifdef _PERFORM_TIMING_ - auto& tmr = const_cast(mTimer); // ugly but this is what root does internally - auto& tmrm = const_cast(mTimerMerge); - LOG(info) << "Inclusive clusterization timing (w/o disk IO): Cpu: " << tmr.CpuTime() - << " Real: " << tmr.RealTime() << " s in " << tmr.Counter() << " slots"; - LOG(info) << "Threads output merging timing : Cpu: " << tmrm.CpuTime() - << " Real: " << tmrm.RealTime() << " s in " << tmrm.Counter() << " slots"; + auto& tmr = const_cast(mTimer); // ugly but this is what root does internally + auto& tmrm = const_cast(mTimerMerge); + LOG(info) << "Inclusive clusterization timing (w/o disk IO): Cpu: " << tmr.CpuTime() + << " Real: " << tmr.RealTime() << " s in " << tmr.Counter() << " slots"; + LOG(info) << "Threads output merging timing : Cpu: " << tmrm.CpuTime() + << " Real: " << tmrm.RealTime() << " s in " << tmrm.Counter() << " slots"; #endif + } } //__________________________________________________ diff --git a/Detectors/ITSMFT/common/reconstruction/src/DigitPixelReader.cxx b/Detectors/ITSMFT/common/reconstruction/src/DigitPixelReader.cxx index b8d88a6fc4223..5c1dbde074649 100644 --- a/Detectors/ITSMFT/common/reconstruction/src/DigitPixelReader.cxx +++ b/Detectors/ITSMFT/common/reconstruction/src/DigitPixelReader.cxx @@ -330,3 +330,14 @@ void DigitPixelReader::clear() mROFRecVec = gsl::span(); mMC2ROFRecVec = gsl::span(); } + +//______________________________________________________________________________ +void DigitPixelReader::reset() +{ + clear(); + mSquashedDigitsMask.clear(); + mBookmarkNextROFs.clear(); + mIdDig = 0; + mIdROF = 0; + mIdROFLast = 0; +} 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 b27739c26bc4d..f4482c651b090 100644 --- a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h +++ b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/DigiParams.h @@ -15,9 +15,11 @@ #ifndef ALICEO2_ITSMFT_DIGIPARAMS_H #define ALICEO2_ITSMFT_DIGIPARAMS_H +#include +#include #include -#include -#include "ITSMFTBase/DPLAlpideParam.h" +#include "ITSMFTSimulation/AlpideSignalTrapezoid.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" //////////////////////////////////////////////////////////// // // @@ -51,24 +53,24 @@ class DigiParams 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 = -1) const { return layer < 0 ? mROFrameLengthInBC : mROFrameLayerLengthInBC[layer]; } + void setROFrameLengthInBC(int n, int layer = -1) { layer < 0 ? mROFrameLengthInBC = n : mROFrameLayerLengthInBC[layer] = n; } - void setROFrameLength(float ns); - float getROFrameLength() const { return mROFrameLength; } - float getROFrameLengthInv() const { return mROFrameLengthInv; } + void setROFrameLength(float ns, int layer = -1); + float getROFrameLength(int layer = -1) const { return layer < 0 ? mROFrameLength : mROFrameLayerLength[layer]; } + float getROFrameLengthInv(int layer = -1) const { return layer < 0 ? mROFrameLengthInv : mROFrameLayerLengthInv[layer]; } void setStrobeDelay(float ns) { mStrobeDelay = ns; } - float getStrobeDelay() const { return mStrobeDelay; } + float getStrobeDelay(int layer = -1) const { return layer < 0 ? mStrobeDelay : mStrobeLayerDelay[layer]; } void setStrobeLength(float ns) { mStrobeLength = ns; } - float getStrobeLength() const { return mStrobeLength; } + float getStrobeLength(int layer = -1) const { return layer < 0 ? mStrobeLength : 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 = -1) { layer < 0 ? mROFrameBiasInBC = n : mROFrameLayerBiasInBC[layer] = n; } + int getROFrameBiasInBC(int layer = -1) const { return layer < 0 ? mROFrameBiasInBC : mROFrameLayerBiasInBC[layer]; } void setChargeThreshold(int v, float frac2Account = 0.1); void setNSimSteps(int v); @@ -96,13 +98,19 @@ class DigiParams const SignalShape& getSignalShape() const { return mSignalShape; } SignalShape& getSignalShape() { return (SignalShape&)mSignalShape; } + bool withStaggering() const noexcept { return !mROFrameLayerLength.empty(); } + void addROFrameLayerLengthInBC(int len) { mROFrameLayerLengthInBC.push_back(len); } + void addROFrameLayerBiasInBC(int len) { mROFrameLayerBiasInBC.push_back(len); } + void addStrobeLength(float ns) { mStrobeLayerLength.push_back(ns); } + void addStrobeDelay(float ns) { mStrobeLayerDelay.push_back(ns); } + virtual void print() const; private: static constexpr double infTime = 1e99; bool mIsContinuous = false; ///< flag for continuous simulation float mNoisePerPixel = 1.e-8; ///< ALPIDE Noise per chip - int mROFrameLengthInBC = 0; ///< ROF length in BC for continuos mode + int mROFrameLengthInBC = 0; ///< ROF length in BC for continuous 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) @@ -115,17 +123,24 @@ class DigiParams float mVbb = 0.0; ///< back bias absolute value for MFT (in Volt) 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) + float mOBVbb = 0.0; ///< back bias absolute value for ITS Outer Barrel (in Volt) + + std::vector mROFrameLayerLengthInBC; ///< staggering ROF length in BC for continuous mode per layer + std::vector mROFrameLayerBiasInBC; ///< staggering ROF bias in BC for continuous mode per layer + std::vector mROFrameLayerLength; ///< staggering ROF length in ns for continuous mode per layer + std::vector mStrobeLayerLength; ///< staggering length of the strobe in ns (sig. over threshold checked in this window only) + std::vector mStrobeLayerDelay; ///< staggering delay of the strobe in ns o2::itsmft::AlpideSignalTrapezoid mSignalShape; ///< signal timeshape parameterization const o2::itsmft::AlpideSimResponse* mAlpSimResponse = nullptr; //!< pointer on external response // auxiliary precalculated parameters - float mROFrameLengthInv = 0; ///< inverse length of RO frame in ns - float mNSimStepsInv = 0; ///< its inverse + float mROFrameLengthInv = 0; ///< inverse length of RO frame in ns + std::vector mROFrameLayerLengthInv; // inverse length of RO frame in ns per layer + float mNSimStepsInv = 0; ///< its inverse - ClassDef(DigiParams, 2); + ClassDef(DigiParams, 3); }; } // namespace itsmft } // namespace o2 diff --git a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h index e3995068c52cf..c81e2d9476644 100644 --- a/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h +++ b/Detectors/ITSMFT/common/simulation/include/ITSMFTSimulation/Digitizer.h @@ -49,6 +49,8 @@ class Digitizer : public TObject public: Digitizer() = default; + Digitizer(Digitizer&&) = delete; + Digitizer& operator=(Digitizer&&) = delete; ~Digitizer() override = default; Digitizer(const Digitizer&) = delete; Digitizer& operator=(const Digitizer&) = delete; @@ -56,27 +58,28 @@ class Digitizer : public TObject void setDigits(std::vector* dig) { mDigits = dig; } void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } void setROFRecords(std::vector* rec) { mROFRecords = rec; } - o2::itsmft::DigiParams& getParams() { return (o2::itsmft::DigiParams&)mParams; } + o2::itsmft::DigiParams& getParams() { return mParams; } const o2::itsmft::DigiParams& getParams() const { return mParams; } void setNoiseMap(const o2::itsmft::NoiseMap* mp) { mNoiseMap = mp; } void setDeadChannelsMap(const o2::itsmft::NoiseMap* mp) { mDeadChanMap = mp; } void init(); + void setAlpideResponse(const o2::itsmft::AlpideSimResponse* resp, int i) { mAlpSimResp[i] = resp; } auto 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); + void process(const std::vector* hits, int evID, int srcID, int layer = -1); + void setEventTime(const o2::InteractionTimeRecord& irt, int layer = -1); double getEndTimeOfROFMax() const { ///< return the time corresponding to end of the last reserved ROFrame : mROFrameMax - return mParams.getROFrameLength() * (mROFrameMax + 1) + mParams.getTimeOffset(); + return (mParams.getROFrameLength() * (double)(mROFrameMax + 1)) + mParams.getTimeOffset(); } void setContinuous(bool v) { mParams.setContinuous(v); } bool isContinuous() const { return mParams.isContinuous(); } - void fillOutputContainer(uint32_t maxFrame = 0xffffffff); + void fillOutputContainer(uint32_t maxFrame = 0xffffffff, int layer = -1); void setDigiParams(const o2::itsmft::DigiParams& par) { mParams = par; } const o2::itsmft::DigiParams& getDigitParams() const { return mParams; } @@ -91,11 +94,17 @@ class Digitizer : public TObject mEventROFrameMin = 0xffffffff; mEventROFrameMax = 0; } + void resetROFrameBounds() + { + mROFrameMin = 0; + mROFrameMax = 0; + mNewROFrame = 0; + } private: - void processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID); + void processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID, int lay); void registerDigits(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 lay); ExtraDig* getExtraDigBuffer(uint32_t roFrame) { @@ -114,7 +123,7 @@ class Digitizer : public TObject o2::itsmft::DigiParams mParams; ///< digitization parameters o2::InteractionTimeRecord mEventTime; ///< global event time and interaction record o2::InteractionRecord mIRFirstSampledTF; ///< IR of the 1st sampled IR, noise-only ROFs will be inserted till this IR only - double mCollisionTimeWrtROF; + double mCollisionTimeWrtROF{}; uint32_t mROFrameMin = 0; ///< lowest RO frame of current digits uint32_t mROFrameMax = 0; ///< highest RO frame of current digits uint32_t mNewROFrame = 0; ///< ROFrame corresponding to provided time @@ -124,11 +133,10 @@ class Digitizer : public TObject uint32_t mEventROFrameMax = 0; ///< highest RO frame forfor processed events (w/o automatic noise ROFs) int mNumberOfChips = 0; - o2::itsmft::AlpideSimResponse* mAlpSimRespMFT = nullptr; - o2::itsmft::AlpideSimResponse* mAlpSimRespIB = nullptr; - o2::itsmft::AlpideSimResponse* mAlpSimRespOB = nullptr; - o2::itsmft::AlpideSimResponse mAlpSimResp[2]; // simulated response - std::string mResponseFile = "$(O2_ROOT)/share/Detectors/ITSMFT/data/AlpideResponseData/AlpideResponseData.root"; + const o2::itsmft::AlpideSimResponse* mAlpSimRespMFT = nullptr; + const o2::itsmft::AlpideSimResponse* mAlpSimRespIB = nullptr; + const o2::itsmft::AlpideSimResponse* mAlpSimRespOB = nullptr; + const o2::itsmft::AlpideSimResponse* mAlpSimResp[2]; // simulated response const o2::itsmft::GeometryTGeo* mGeometry = nullptr; ///< ITS OR MFT upgrade geometry std::vector mChips; ///< Array of chips digits containers diff --git a/Detectors/ITSMFT/common/simulation/src/DigiParams.cxx b/Detectors/ITSMFT/common/simulation/src/DigiParams.cxx index ffba627265cc7..a7c5c32b6351d 100644 --- a/Detectors/ITSMFT/common/simulation/src/DigiParams.cxx +++ b/Detectors/ITSMFT/common/simulation/src/DigiParams.cxx @@ -26,12 +26,17 @@ 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; + assert(lNS > 1.f); + if (layer < 0) { + mROFrameLength = lNS; + mROFrameLengthInv = 1.f / mROFrameLength; + } else { + mROFrameLayerLength.push_back(lNS); + mROFrameLayerLengthInv.push_back(1.f / lNS); + } } void DigiParams::setNSimSteps(int v) @@ -58,17 +63,24 @@ void DigiParams::setChargeThreshold(int v, float frac2Account) //______________________________________________ void DigiParams::print() const { - // print settings - printf("Alpide 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); - printf("ELoss to N electrons factor : %e\n", mEnergyToNElectrons); - printf("Noise level per pixel : %e\n", mNoisePerPixel); - printf("Charge time-response:\n"); + LOGF(info, "Alpide digitization params:"); + LOGF(info, "Continuous readout : %s", mIsContinuous ? "ON" : "OFF"); + if (withStaggering()) { + for (int i{0}; i < (int)mROFrameLayerLengthInBC.size(); ++i) { + LOGF(info, " Readout Frame Layer:%d Length(ns)[BC] : %f [%d]", i, mROFrameLayerLength[i], mROFrameLayerLengthInBC[i]); + LOGF(info, "Strobe delay Layer %d (ns) : %f", i, mStrobeDelay); + LOGF(info, "Strobe length Layer %d (ns) : %f", i, mStrobeLength); + } + } else { + LOGF(info, "Readout Frame Length(ns) : %f", mROFrameLength); + LOGF(info, "Strobe delay (ns) : %f", mStrobeDelay); + LOGF(info, "Strobe length (ns) : %f", mStrobeLength); + } + LOGF(info, "Threshold (N electrons) : %d", mChargeThreshold); + LOGF(info, "Min N electrons to account : %d", mMinChargeToAccount); + LOGF(info, "Number of charge sharing steps : %d", mNSimSteps); + LOGF(info, "ELoss to N electrons factor : %e", mEnergyToNElectrons); + LOGF(info, "Noise level per pixel : %e", mNoisePerPixel); + LOGF(info, "Charge time-response:"); mSignalShape.print(); } diff --git a/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx b/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx index 382fa769a94c7..b1a92e988968b 100644 --- a/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx +++ b/Detectors/ITSMFT/common/simulation/src/Digitizer.cxx @@ -13,6 +13,7 @@ /// \brief Implementation of the ITS/MFT digitizer #include "DataFormatsITSMFT/Digit.h" +#include "Framework/Logger.h" #include "ITSMFTBase/SegmentationAlpide.h" #include "ITSMFTSimulation/DPLDigitizerParam.h" #include "ITSMFTSimulation/Digitizer.h" @@ -21,10 +22,11 @@ #include "DetectorsRaw/HBFUtils.h" #include +#include #include #include +#include #include -#include // for LOG using o2::itsmft::Digit; using o2::itsmft::Hit; @@ -48,24 +50,6 @@ void Digitizer::init() mChips[i].setDeadChanMap(mDeadChanMap); } } - // initializing for both collection tables - /*for (int i = 0; i < 2; i++) { - mAlpSimResp[i].initData(i); - }*/ - - // importing the charge collection tables - // (initialized while building O2) - auto file = TFile::Open(mResponseFile.data()); - if (!file) { - LOG(fatal) << "Cannot open response file " << mResponseFile; - } - /*std::string response = "response"; - for (int i=0; i<2; i++) { - response.append(std::to_string(i)); - mAlpSimResp[i] = *(o2::itsmft::AlpideSimResponse*)file->Get(response.data()); - }*/ - mAlpSimResp[0] = *(o2::itsmft::AlpideSimResponse*)file->Get("response0"); - mAlpSimResp[1] = *(o2::itsmft::AlpideSimResponse*)file->Get("response1"); // importing the parameters from DPLDigitizerParam.h auto& doptMFT = DPLDigitizerParam::Instance(); @@ -73,32 +57,32 @@ void Digitizer::init() // initializing response according to detector and back-bias value if (doptMFT.Vbb == 0.0) { // for MFT - mAlpSimRespMFT = mAlpSimResp; + mAlpSimRespMFT = mAlpSimResp[0]; LOG(info) << "Choosing Vbb=0V for MFT"; } else if (doptMFT.Vbb == 3.0) { - mAlpSimRespMFT = mAlpSimResp + 1; + mAlpSimRespMFT = mAlpSimResp[1]; LOG(info) << "Choosing Vbb=-3V for MFT"; } else { LOG(fatal) << "Invalid MFT back-bias value"; } if (doptITS.IBVbb == 0.0) { // for ITS Inner Barrel - mAlpSimRespIB = mAlpSimResp; + mAlpSimRespIB = mAlpSimResp[0]; LOG(info) << "Choosing Vbb=0V for ITS IB"; } else if (doptITS.IBVbb == 3.0) { - mAlpSimRespIB = mAlpSimResp + 1; + mAlpSimRespIB = mAlpSimResp[1]; LOG(info) << "Choosing Vbb=-3V for ITS IB"; } else { LOG(fatal) << "Invalid ITS Inner Barrel back-bias value"; } - if (doptITS.OBVbb == 0.0) { // for ITS Outter Barrel - mAlpSimRespOB = mAlpSimResp; + if (doptITS.OBVbb == 0.0) { // for ITS Outer Barrel + mAlpSimRespOB = mAlpSimResp[0]; LOG(info) << "Choosing Vbb=0V for ITS OB"; } else if (doptITS.OBVbb == 3.0) { - mAlpSimRespOB = mAlpSimResp + 1; + mAlpSimRespOB = mAlpSimResp[1]; LOG(info) << "Choosing Vbb=-3V for ITS OB"; } else { - LOG(fatal) << "Invalid ITS Outter Barrel back-bias value"; + LOG(fatal) << "Invalid ITS Outer Barrel back-bias value"; } mParams.print(); mIRFirstSampledTF = o2::raw::HBFUtils::Instance().getFirstSampledTFIR(); @@ -116,47 +100,53 @@ auto Digitizer::getChipResponse(int chipID) if (chipID < 432) { // in ITS Inner Barrel return mAlpSimRespIB; - } else { // in ITS Outter Barrel + } else { // in ITS Outer Barrel return mAlpSimRespOB; } } //_______________________________________________________________________ -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 + // opt. apply a filter on the layer of the processed hits - LOG(info) << "Digitizing " << mGeometry->getName() << " hits of entry " << evID << " from source " - << srcID << " at time " << mEventTime << " ROFrame= " << mNewROFrame << ")" - << " cont.mode: " << isContinuous() - << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; + LOG(debug) << "Digitizing " << mGeometry->getName() << ":" << layer << " hits of entry " << evID << " from source " + << srcID << " at time " << mEventTime << " ROFrame= " << mNewROFrame << ")" + << " cont.mode: " << isContinuous() + << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; // is there something to flush ? if (mNewROFrame > mROFrameMin) { - fillOutputContainer(mNewROFrame - 1); // flush out all frame preceding the new one + fillOutputContainer(mNewROFrame - 1, layer); // flush out all frame preceding the new one } int nHits = hits->size(); std::vector hitIdx(nHits); std::iota(std::begin(hitIdx), std::end(hitIdx), 0); // sort hits to improve memory access - std::sort(hitIdx.begin(), hitIdx.end(), - [hits](auto lhs, auto rhs) { - return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); - }); - for (int i : hitIdx) { - processHit((*hits)[i], mROFrameMax, evID, srcID); + std::sort(hitIdx.begin(), hitIdx.end(), [hits](auto lhs, auto rhs) { + return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID(); + }); + for (int i : hitIdx | std::views::filter([&](int idx) { + if (layer < 0) { + return true; + } + return mGeometry->getLayer((*hits)[idx].GetDetectorID()) == layer; + })) { + processHit((*hits)[i], mROFrameMax, evID, srcID, layer); } + // 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); + fillOutputContainer(mROFrameMax, layer); } } //_______________________________________________________________________ -void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt) +void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt, int layer) { // assign event time in ns mEventTime = irt; @@ -179,13 +169,13 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt) // this event is before the first RO mIsBeforeFirstRO = true; } else { - mNewROFrame = nbc / mParams.getROFrameLengthInBC(); + mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer); mIsBeforeFirstRO = false; } - LOG(info) << " NewROFrame " << mNewROFrame << " nbc " << nbc; + LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc; // in continuous mode depends on starts of periodic readout frame - mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC()) * o2::constants::lhc::LHCBunchSpacingNS; + mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS; } else { mNewROFrame = 0; } @@ -201,16 +191,14 @@ void Digitizer::setEventTime(const o2::InteractionTimeRecord& irt) } //_______________________________________________________________________ -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) { - frameLast = mROFrameMax; - } + frameLast = std::min(frameLast, mROFrameMax); // make sure all buffers for extra digits are created up to the maxFrame getExtraDigBuffer(mROFrameMax); - LOG(info) << "Filling " << mGeometry->getName() << " digits output for RO frames " << mROFrameMin << ":" + LOG(info) << "Filling " << mGeometry->getName() << " digits:" << layer << " output for RO frames " << mROFrameMin << ":" << frameLast; o2::itsmft::ROFRecord rcROF; @@ -222,7 +210,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->getLayer(chip.getChipIndex()) != layer)) { continue; } chip.addNoise(mROFrameMin, mROFrameMin, &mParams); @@ -254,7 +242,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()); + rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC(layer)); } else { rcROF.getBCData() = mEventTime; // RSTODO do we need to add trigger delay? } @@ -269,10 +257,10 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID) +void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID, int lay) { // convert single hit to digits - int chipID = hit.GetDetectorID(); + auto chipID = hit.GetDetectorID(); auto& chip = mChips[chipID]; if (chip.isDisabled()) { LOG(debug) << "skip disabled chip " << chipID; @@ -302,14 +290,12 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID } 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(lay)); // 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 = mParams.isContinuous() ? (timeInROF + tTot) * mParams.getROFrameLengthInv(lay) : roFrameRel; int nFrames = roFrameRelMax + 1 - roFrameRel; uint32_t roFrameMax = mNewROFrame + roFrameRelMax; - if (roFrameMax > maxFr) { - maxFr = roFrameMax; // if signal extends beyond current maxFrame, increase the latter - } + maxFr = std::max(roFrameMax, maxFr); // if signal extends beyond current maxFrame, increase the latter // here we start stepping in the depth of the sensor to generate charge diffusion float nStepsInv = mParams.getNSimStepsInv(); @@ -350,17 +336,13 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID } rowS -= AlpideRespSimMat::NPix / 2; rowE += AlpideRespSimMat::NPix / 2; - if (rowS < 0) { - rowS = 0; - } + rowS = std::max(rowS, 0); if (rowE >= Segmentation::NRows) { rowE = Segmentation::NRows - 1; } colS -= AlpideRespSimMat::NPix / 2; colE += AlpideRespSimMat::NPix / 2; - if (colS < 0) { - colS = 0; - } + colS = std::max(colS, 0); if (colE >= Segmentation::NCols) { colE = Segmentation::NCols - 1; } @@ -380,7 +362,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID const o2::itsmft::AlpideSimResponse* resp = getChipResponse(chipID); - // take into account that the AlpideSimResponse depth defintion has different min/max boundaries + // take into account that the AlpideSimResponse depth definition has different min/max boundaries // although the max should coincide with the surface of the epitaxial layer, which in the chip // local coordinates has Y = +SensorLayerThickness/2 @@ -397,7 +379,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID rowPrev = row; colPrev = col; } - bool flipCol, flipRow; + bool flipCol = false, flipRow = false; // note that response needs coordinates along column row (locX) (locZ) then depth (locY) auto rspmat = resp->getResponse(xyzLocS.X() - cRowPix, xyzLocS.Z() - cColPix, xyzLocS.Y(), flipRow, flipCol); @@ -407,12 +389,12 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID } for (int irow = AlpideRespSimMat::NPix; irow--;) { - int rowDest = row + irow - AlpideRespSimMat::NPix / 2 - rowS; // destination row in the respMatrix + int rowDest = row + irow - (AlpideRespSimMat::NPix / 2) - rowS; // destination row in the respMatrix if (rowDest < 0 || rowDest >= rowSpan) { continue; } for (int icol = AlpideRespSimMat::NPix; icol--;) { - int colDest = col + icol - AlpideRespSimMat::NPix / 2 - colS; // destination column in the respMatrix + int colDest = col + icol - (AlpideRespSimMat::NPix / 2) - colS; // destination column in the respMatrix if (colDest < 0 || colDest >= colSpan) { continue; } @@ -444,35 +426,31 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID continue; } // - registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl); + registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl, lay); } } } //________________________________________________________________________________ void Digitizer::registerDigits(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 lay) { // 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 - float tStrobe = mParams.getStrobeDelay() - tInROF; // strobe start wrt signal start + float tStrobe = mParams.getStrobeDelay(lay) - tInROF; // strobe start wrt signal start for (int i = 0; i < nROF; i++) { 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(lay)); + tStrobe += mParams.getROFrameLength(lay); // for the next ROF // discard too small contributions, they have no chance to produce a digit if (nEleROF < mParams.getMinChargeToAccount()) { continue; } - if (roFr > mEventROFrameMax) { - mEventROFrameMax = roFr; - } - if (roFr < mEventROFrameMin) { - mEventROFrameMin = roFr; - } + mEventROFrameMax = std::max(roFr, mEventROFrameMax); + mEventROFrameMin = std::min(roFr, mEventROFrameMin); auto key = chip.getOrderingKey(roFr, row, col); PreDigit* pd = chip.findDigit(key); if (!pd) { diff --git a/Detectors/ITSMFT/common/workflow/CMakeLists.txt b/Detectors/ITSMFT/common/workflow/CMakeLists.txt index 63cd8d6c0bcee..ead08c4422260 100644 --- a/Detectors/ITSMFT/common/workflow/CMakeLists.txt +++ b/Detectors/ITSMFT/common/workflow/CMakeLists.txt @@ -11,6 +11,8 @@ o2_add_library(ITSMFTWorkflow SOURCES src/ClusterReaderSpec.cxx + src/ClusterWriterSpec.cxx + src/ClustererSpec.cxx src/DigitWriterSpec.cxx src/DigitReaderSpec.cxx src/STFDecoderSpec.cxx diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterReaderSpec.h index 99318df1cd9d9..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,80 +25,77 @@ #include "DataFormatsITSMFT/CompCluster.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/MCTruthContainer.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/ROFRecord.h" #include "DetectorsCommonDataFormats/DetID.h" using namespace o2::framework; -namespace o2 -{ -namespace itsmft +namespace o2::itsmft { +template 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}; + ClusterReader() = delete; - ClusterReader(o2::detectors::DetID id, 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; protected: void connectTree(const std::string& filename); + template + void setBranchAddress(const std::string& base, Ptr& addr, int layer); + std::string getBranchName(const std::string& base, int index) const; - std::vector mClusROFRec, *mClusROFRecPtr = &mClusROFRec; - std::vector mClusterCompArray, *mClusterCompArrayPtr = &mClusterCompArray; - std::vector mPatternsArray, *mPatternsArrayPtr = &mPatternsArray; - o2::dataformats::MCTruthContainer mClusterMCTruth, *mClusterMCTruthPtr = &mClusterMCTruth; - std::vector mClusMC2ROFs, *mClusMC2ROFsPtr = &mClusMC2ROFs; - - o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; + 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 = ""; - std::string mFileName = ""; + std::string mDetName; + std::string mDetNameLC; + std::string mFileName; std::string mClusTreeName = "o2sim"; std::string mClusROFBranchName = "ClustersROF"; std::string mClusterPattBranchName = "ClusterPatt"; std::string mClusterCompBranchName = "ClusterComp"; std::string mClustMCTruthBranchName = "ClusterMCTruth"; - std::string mClustMC2ROFBranchName = "ClustersMC2ROF"; }; -class ITSClusterReader : public ClusterReader +class ITSClusterReader : public ClusterReader { public: - ITSClusterReader(bool useMC = true, bool usePatterns = true, bool triggerOut = true) - : ClusterReader(o2::detectors::DetID::ITS, useMC, usePatterns, triggerOut) - { - mOrigin = o2::header::gDataOriginITS; - } + ITSClusterReader(bool useMC = true, bool doStag = false, bool usePatterns = true, bool triggerOut = true) + : ClusterReader(useMC, doStag, usePatterns, triggerOut) {} }; -class MFTClusterReader : public ClusterReader +class MFTClusterReader : public ClusterReader { public: - MFTClusterReader(bool useMC = true, bool usePatterns = true, bool triggerOut = true) - : ClusterReader(o2::detectors::DetID::MFT, useMC, usePatterns, triggerOut) - { - mOrigin = o2::header::gDataOriginMFT; - } + 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 itsmft -} // namespace o2 +} // namespace o2::itsmft #endif /* O2_ITSMFT_CLUSTERREADER */ diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h new file mode 100644 index 0000000000000..e43e5def18482 --- /dev/null +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClusterWriterSpec.h @@ -0,0 +1,29 @@ +// 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 ClusterWriterSpec.h + +#ifndef O2_ITSMFT_CLUSTERWRITER +#define O2_ITSMFT_CLUSTERWRITER + +#include "Framework/DataProcessorSpec.h" + +namespace o2::itsmft +{ + +template +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 + +#endif /* O2_ITS_CLUSTERWRITER */ diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClustererSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h similarity index 66% rename from Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClustererSpec.h rename to Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h index f0a763597ff74..5535ecb42d645 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClustererSpec.h +++ b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/ClustererSpec.h @@ -11,10 +11,9 @@ /// @file ClustererSpec.h -#ifndef O2_MFT_CLUSTERERDPL_H_ -#define O2_MFT_CLUSTERERDPL_H_ +#ifndef O2_ITSMFT_CLUSTERERDPL_H_ +#define O2_ITSMFT_CLUSTERERDPL_H_ -#include #include "DetectorsBase/GRPGeomHelper.h" #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" @@ -22,15 +21,17 @@ using namespace o2::framework; -namespace o2 -{ -namespace mft +namespace o2::itsmft { +template 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}; + 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; @@ -39,20 +40,20 @@ class ClustererDPL : public Task private: void updateTimeDependentParams(ProcessingContext& pc); - int mState = 0; + std::string mDetName; bool mUseMC = true; bool mUseClusterDictionary = true; int mNThreads = 1; - std::unique_ptr mFile = nullptr; std::unique_ptr mClusterer = nullptr; std::shared_ptr mGGCCDBRequest; + bool mDoStaggering = false; + int mLayers = 1; + std::vector mFilter; }; -/// create a processor spec -/// run MFT cluster finder -framework::DataProcessorSpec getClustererSpec(bool useMC); +framework::DataProcessorSpec getITSClustererSpec(bool useMC, bool doStag); +framework::DataProcessorSpec getMFTClustererSpec(bool useMC, bool doStag); -} // namespace mft -} // namespace o2 +} // namespace o2::itsmft #endif /* O2_MFT_CLUSTERERDPL */ diff --git a/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h b/Detectors/ITSMFT/common/workflow/include/ITSMFTWorkflow/DigitReaderSpec.h index e655e05842d71..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,8 +14,12 @@ #ifndef O2_ITSMFT_DIGITREADER #define O2_ITSMFT_DIGITREADER -#include "TFile.h" -#include "TTree.h" +#include + +#include +#include + +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" #include "DataFormatsITSMFT/ROFRecord.h" @@ -34,70 +38,71 @@ namespace o2 namespace itsmft { +template 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}; + DigitReader() = delete; - DigitReader(o2::detectors::DetID id, 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; 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); - std::vector mDigits, *mDigitsPtr = &mDigits; + std::vector*> mDigits{nullptr}; std::vector mCalib, *mCalibPtr = &mCalib; - std::vector mDigROFRec, *mDigROFRecPtr = &mDigROFRec; - std::vector mDigMC2ROFs, *mDigMC2ROFsPtr = &mDigMC2ROFs; - o2::dataformats::ConstMCTruthContainer mConstLabels; - o2::header::DataOrigin mOrigin = o2::header::gDataOriginInvalid; + 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; - std::string mDetName = ""; - std::string mDetNameLC = ""; - std::string mFileName = ""; + int mLayers = 1; + std::string mDetName; + std::string mDetNameLC; + std::string mFileName; std::string mDigTreeName = "o2sim"; std::string mDigitBranchName = "Digit"; - std::string mDigROFBranchName = "DigitROF"; + std::string mDigitROFBranchName = "DigitROF"; std::string mCalibBranchName = "Calib"; - std::string mDigtMCTruthBranchName = "DigitMCTruth"; - std::string mDigtMC2ROFBranchName = "DigitMC2ROF"; + std::string mDigitMCTruthBranchName = "DigitMCTruth"; }; -class ITSDigitReader : public DigitReader +class ITSDigitReader : public DigitReader { public: - ITSDigitReader(bool useMC = true, bool useCalib = false, bool useTriggers = true) - : DigitReader(o2::detectors::DetID::ITS, useMC, useCalib, useTriggers) - { - mOrigin = o2::header::gDataOriginITS; - } + ITSDigitReader(bool useMC = true, bool doStag = false, bool useCalib = false, bool useTriggers = true) + : DigitReader(useMC, doStag, useCalib, useTriggers) {} }; -class MFTDigitReader : public DigitReader +class MFTDigitReader : public DigitReader { public: - MFTDigitReader(bool useMC = true, bool useCalib = false, bool useTriggers = true) - : DigitReader(o2::detectors::DetID::MFT, useMC, useCalib, useTriggers) - { - mOrigin = o2::header::gDataOriginMFT; - } + 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 4ed4e99f4b6f8..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); + 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); +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 c10ae16c95a3e..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); + 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); +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 ea906056c7898..6174938171336 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx @@ -12,15 +12,16 @@ /// @file ClusterReaderSpec.cxx #include +#include -#include "TTree.h" +#include #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/Logger.h" #include "ITSMFTWorkflow/ClusterReaderSpec.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DataFormatsITSMFT/PhysTrigger.h" -#include #include "CommonUtils/NameConf.h" using namespace o2::framework; @@ -31,45 +32,51 @@ namespace o2 namespace itsmft { -ClusterReader::ClusterReader(o2::detectors::DetID id, bool useMC, bool usePatterns, bool triggerOut) +template +ClusterReader::ClusterReader(bool useMC, bool doStag, bool usePatterns, bool triggerOut) : mUseMC(useMC), mUsePatterns(usePatterns), mTriggerOut(triggerOut), mDetName(Origin.as()), mDetNameLC(mDetName) { - assert(id == o2::detectors::DetID::ITS || id == o2::detectors::DetID::MFT); - mDetNameLC = mDetName = id.getName(); - mUseMC = useMC; - mUsePatterns = usePatterns; - mTriggerOut = triggerOut; std::transform(mDetNameLC.begin(), mDetNameLC.end(), mDetNameLC.begin(), ::tolower); + if (doStag) { + mLayers = DPLAlpideParam::getNLayers(); + mClusROFRec.resize(mLayers, nullptr); + mClusterCompArray.resize(mLayers, nullptr); + mPatternsArray.resize(mLayers, nullptr); + mClusterMCTruth.resize(mLayers, nullptr); + } } -void ClusterReader::init(InitContext& ic) +template +void ClusterReader::init(InitContext& ic) { mFileName = o2::utils::Str::concat_string(o2::utils::Str::rectifyDirectory(ic.options().get("input-dir")), ic.options().get((mDetNameLC + "-cluster-infile").c_str())); connectTree(mFileName); } -void ClusterReader::run(ProcessingContext& pc) +template +void ClusterReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; assert(ent < mTree->GetEntries()); // this should not happen mTree->GetEntry(ent); - LOG(info) << mDetName << "ClusterReader pushes " << mClusROFRec.size() << " ROFRecords," - << mClusterCompArray.size() << " compact clusters 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, "CLUSTERSROF", 0}, mClusROFRec); - pc.outputs().snapshot(Output{mOrigin, "COMPCLUSTERS", 0}, mClusterCompArray); - if (mUsePatterns) { - pc.outputs().snapshot(Output{mOrigin, "PATTERNS", 0}, mPatternsArray); - } - if (mUseMC) { - pc.outputs().snapshot(Output{mOrigin, "CLUSTERSMCTR", 0}, mClusterMCTruth); - pc.outputs().snapshot(Output{mOrigin, "CLUSTERSMC2ROF", 0}, mClusMC2ROFs); + + 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) { + pc.outputs().snapshot(Output{Origin, "PATTERNS", iLayer}, *mPatternsArray[iLayer]); + } + if (mUseMC) { + pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, *mClusterMCTruth[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) { std::vector dummyTrig; - pc.outputs().snapshot(Output{mOrigin, "PHYSTRIG", 0}, dummyTrig); + pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, dummyTrig); } if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); @@ -77,7 +84,8 @@ void ClusterReader::run(ProcessingContext& pc) } } -void ClusterReader::connectTree(const std::string& filename) +template +void ClusterReader::connectTree(const std::string& filename) { mTree.reset(nullptr); // in case it was already loaded mFile.reset(TFile::Open(filename.c_str())); @@ -85,70 +93,87 @@ void ClusterReader::connectTree(const std::string& filename) mTree.reset((TTree*)mFile->Get(mClusTreeName.c_str())); assert(mTree); - mTree->SetBranchAddress((mDetName + mClusROFBranchName).c_str(), &mClusROFRecPtr); - mTree->SetBranchAddress((mDetName + mClusterCompBranchName).c_str(), &mClusterCompArrayPtr); - if (mUsePatterns) { - mTree->SetBranchAddress((mDetName + mClusterPattBranchName).c_str(), &mPatternsArrayPtr); - } - if (mUseMC) { - if (mTree->GetBranch((mDetName + mClustMCTruthBranchName).c_str()) && - mTree->GetBranch((mDetName + mClustMC2ROFBranchName).c_str())) { - mTree->SetBranchAddress((mDetName + mClustMCTruthBranchName).c_str(), &mClusterMCTruthPtr); - mTree->SetBranchAddress((mDetName + mClustMC2ROFBranchName).c_str(), &mClusMC2ROFsPtr); - } else { - LOG(info) << "MC-truth is missing"; - mUseMC = false; + 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())) { + setBranchAddress(mClustMCTruthBranchName, mClusterMCTruth[iLayer], iLayer); + } else { + LOG(info) << "MC-truth is missing"; + mUseMC = false; + } } } LOG(info) << "Loaded tree from " << filename << " with " << mTree->GetEntries() << " entries"; } -DataProcessorSpec getITSClusterReaderSpec(bool useMC, bool usePatterns, bool triggerOut) +template +std::string ClusterReader::getBranchName(const std::string& base, int index) const +{ + if (mDoStaggering) { + return mDetName + base + "_" + std::to_string(index); + } + return mDetName + base; +} + +template +template +void ClusterReader::setBranchAddress(const std::string& base, Ptr& addr, int layer) { - std::vector outputSpec; - outputSpec.emplace_back("ITS", "CLUSTERSROF", 0, Lifetime::Timeframe); - outputSpec.emplace_back("ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); - if (usePatterns) { - outputSpec.emplace_back("ITS", "PATTERNS", 0, Lifetime::Timeframe); + 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); } - if (useMC) { - outputSpec.emplace_back("ITS", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("ITS", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); +} + +namespace +{ +template +std::vector makeOutChannels(o2::header::DataOrigin detOrig, bool mctruth, bool doStag, bool usePatterns, bool triggerOut) +{ + std::vector outputs; + 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) { + outputs.emplace_back(detOrig, "PATTERNS", iLayer, Lifetime::Timeframe); + } + if (mctruth) { + outputs.emplace_back(detOrig, "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + outputs.emplace_back(detOrig, "CLUSTERSMC2ROF", iLayer, Lifetime::Timeframe); + } } if (triggerOut) { - outputSpec.emplace_back("ITS", "PHYSTRIG", 0, Lifetime::Timeframe); + outputs.emplace_back(detOrig, "PHYSTRIG", 0, Lifetime::Timeframe); } + return outputs; +} +} // namespace + +DataProcessorSpec getITSClusterReaderSpec(bool useMC, bool doStag, bool usePatterns, bool triggerOut) +{ return DataProcessorSpec{ - "its-cluster-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC, usePatterns, triggerOut)}, - Options{ + .name = "its-cluster-reader", + .inputs = Inputs{}, + .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) { - std::vector outputSpec; - outputSpec.emplace_back("MFT", "CLUSTERSROF", 0, Lifetime::Timeframe); - outputSpec.emplace_back("MFT", "COMPCLUSTERS", 0, Lifetime::Timeframe); - if (usePatterns) { - outputSpec.emplace_back("MFT", "PATTERNS", 0, Lifetime::Timeframe); - } - if (useMC) { - outputSpec.emplace_back("MFT", "CLUSTERSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("MFT", "CLUSTERSMC2ROF", 0, Lifetime::Timeframe); - } - if (triggerOut) { - outputSpec.emplace_back("MFT", "PHYSTRIG", 0, Lifetime::Timeframe); - } return DataProcessorSpec{ - "mft-cluster-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC, usePatterns, triggerOut)}, - Options{ + .name = "mft-cluster-reader", + .inputs = Inputs{}, + .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 new file mode 100644 index 0000000000000..b189e9c644e27 --- /dev/null +++ b/Detectors/ITSMFT/common/workflow/src/ClusterWriterSpec.cxx @@ -0,0 +1,132 @@ +// 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 ClusterWriterSpec.cxx + +#include +#include +#include +#include +#include + +#include "Framework/ConcreteDataMatcher.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" +#include "ITSMFTWorkflow/ClusterWriterSpec.h" +#include "DPLUtils/MakeRootTreeWriterSpec.h" +#include "DataFormatsITSMFT/CompCluster.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" + +using namespace o2::framework; + +namespace o2::itsmft +{ + +template +using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; +using CompClusType = std::vector; +using PatternsType = std::vector; +using ROFrameRType = std::vector; +using LabelsType = o2::dataformats::MCTruthContainer; +using ROFRecLblT = std::vector; +using namespace o2::header; + +template +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}; + const int nLayers = (doStag) ? DPLAlpideParam::getNLayers() : 1; + const auto detName = Origin.as(); + // Spectators for logging + 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, doStag](std::vector const& rofs, DataRef const& ref) { + auto const* dh = DataRefUtils::getHeader(ref); + const auto i = dh->subSpecification; + 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 = [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{vecInpSpecClus, + (detName + "ClusterComp").c_str(), "compact-cluster-branch", + nLayers, + compClustersSizeGetter, + getIndex, + getName}, + BranchDefinition{vecInpSpecPatt, + (detName + "ClusterPatt").c_str(), "cluster-pattern-branch", + nLayers, + getIndex, + getName}, + BranchDefinition{vecInpSpecROF, + (detName + "ClustersROF").c_str(), "cluster-rof-branch", + nLayers, + logger, + getIndex, + getName}, + BranchDefinition{vecInpSpecLbl, + (detName + "ClusterMCTruth").c_str(), "cluster-label-branch", + (useMC ? nLayers : 0), + getIndex, + getName}, + BranchDefinition{InputSpec{"MC2ROframes", ConcreteDataTypeMatcher{Origin, "CLUSTERSMC2ROF"}}, + (detName + "ClustersMC2ROF").c_str(), "cluster-mc2rof-branch", + (useMC ? nLayers : 0), + getIndex, + getName})(); +} + +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 new file mode 100644 index 0000000000000..b3954c5c22ad1 --- /dev/null +++ b/Detectors/ITSMFT/common/workflow/src/ClustererSpec.cxx @@ -0,0 +1,336 @@ +// 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 ClustererSpec.cxx + +#include +#include + +#include "ITSMFTWorkflow/ClustererSpec.h" +#include "Framework/ControlService.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/CCDBParamSpec.h" +#include "DataFormatsITSMFT/Digit.h" +#include "Framework/InputRecordWalker.h" +#include "ITSMFTReconstruction/ChipMappingMFT.h" +#include "ITSMFTReconstruction/ChipMappingITS.h" +#include "DataFormatsITSMFT/CompCluster.h" +#include "DataFormatsITSMFT/TopologyDictionary.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsParameters/GRPObject.h" +#include "ITSMFTReconstruction/DigitPixelReader.h" +#include "DetectorsBase/GeometryManager.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" +#include "CommonConstants/LHCConstants.h" +#include "DetectorsCommonDataFormats/DetectorNameConf.h" +#include "ITSMFTReconstruction/ClustererParam.h" + +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) +{ + mClusterer = std::make_unique(); + mClusterer->setNChips((N == o2::detectors::DetID::ITS) ? o2::itsmft::ChipMappingITS::getNChips() : o2::itsmft::ChipMappingMFT::getNChips()); + mUseClusterDictionary = !ic.options().get("ignore-cluster-dictionary"); + o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); + mNThreads = std::max(1, ic.options().get("nthreads")); + mDetName = Origin.as(); + + // prepare data filter + 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); + } + } +} + +template +void ClustererDPL::run(ProcessingContext& pc) +{ + updateTimeDependentParams(pc); + + // filter input and compose + 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"}})) { + digits[dh->subSpecification] = pc.inputs().get>(ref); + } + if (DataRefUtils::match(ref, {"ROframe", ConcreteDataTypeMatcher{Origin, "DIGITSROF"}})) { + rofs[dh->subSpecification] = pc.inputs().get>(ref); + } + if (DataRefUtils::match(ref, {"labels", ConcreteDataTypeMatcher{Origin, "DIGITSMCTR"}})) { + labelsbuffer[dh->subSpecification] = pc.inputs().get>(ref); + } + } + + // query the first orbit in this TF + const auto firstTForbit = pc.services().get().firstTForbit; + const o2::InteractionRecord firstIR(0, firstTForbit); + const auto& par = DPLAlpideParam::Instance(); + + // process received inputs + uint64_t nClusters{0}; + TStopwatch sw; + o2::itsmft::DigitPixelReader reader; + for (uint32_t iLayer{0}; iLayer < mLayers; ++iLayer) { + int layer = (mDoStaggering) ? iLayer : -1; + sw.Start(); + 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]); + reader.setSquashingDepth(mClusterer->getMaxROFDepthToSquash(layer)); + reader.setSquashingDist(mClusterer->getMaxRowColDiffToMask()); // Sharing same parameter/logic with masking + reader.setMaxBCSeparationToSquash(mClusterer->getMaxBCSeparationToSquash(layer)); + reader.setDigits(digits[iLayer]); + reader.setROFRecords(rofs[iLayer]); + if (mUseMC) { + LOG(info) << mDetName << "Clusterer" << ((mDoStaggering) ? std::format(" on layer {}", layer) : "") << " pulled " << labels.getNElements() << " labels "; + reader.setDigitsMCTruth(labels.getIndexedSize() > 0 ? &labels : nullptr); + } + reader.init(); + std::vector clusCompVec; + std::vector clusROFVec; + std::vector clusPattVec; + + std::unique_ptr> clusterLabels; + if (mUseMC) { + clusterLabels = std::make_unique>(); + } + mClusterer->process(mNThreads, reader, &clusCompVec, &clusPattVec, &clusROFVec, clusterLabels.get()); + + // ensure that the rof output is continuous + size_t nROFs = clusROFVec.size(); + const int nROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / par.getROFLengthInBC(iLayer); + const int nROFsTF = nROFsPerOrbit * o2::base::GRPGeomHelper::getNHBFPerTF(); + // It can happen that in the digitization rofs without contributing hits are skipped or there are stray ROFs + // We will preserve the clusters as they are but the stray ROFs will be removed (leaving their clusters unaddressed). + std::vector expClusRofVec(nROFsTF); + 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 + par.getROFDelayInBC(iLayer); + 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 : clusROFVec) { + const auto& ir = rof.getBCData(); + if (ir < firstIR) { + 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; + } + irToFirst -= par.getROFDelayInBC(iLayer); + const long irROF = irToFirst.toLong() / par.getROFLengthInBC(iLayer); + if (irROF >= nROFsTF) { + LOGP(warn, "Discard ROF {} exceeding TF orbit range{}", ir.asString(), ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); + continue; + } + auto& expROF = expClusRofVec[irROF]; + if (expROF.getNEntries() == 0) { + expROF.setFirstEntry(rof.getFirstEntry()); + expROF.setNEntries(rof.getNEntries()); + } else { + if (expROF.getNEntries() < rof.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 {} with {} clusters, discard preferring already processed instance with {} clusters{}", rof.asString(), rof.getNEntries(), expROF.getNEntries(), ((mDoStaggering) ? std::format(" on layer {}", layer) : "")); + } + } + } + int prevLast{0}; + for (auto& rof : expClusRofVec) { + if (rof.getFirstEntry() < 0) { + rof.setFirstEntry(prevLast); + } + prevLast = rof.getFirstEntry() + rof.getNEntries(); + } + nROFs = expClusRofVec.size(); + pc.outputs().snapshot(Output{Origin, "CLUSTERSROF", iLayer}, expClusRofVec); + + pc.outputs().snapshot(Output{Origin, "COMPCLUSTERS", iLayer}, clusCompVec); + pc.outputs().snapshot(Output{Origin, "PATTERNS", iLayer}, clusPattVec); + + nClusters += clusCompVec.size(); + + if (mUseMC) { + pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, *clusterLabels); // at the moment requires snapshot + // write dummy MC2ROF vector to keep writer/readers backward compatible + static std::vector dummyMC2ROF; + pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, dummyMC2ROF); + } + reader.reset(); + + sw.Stop(); + 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"; +} + +///_______________________________________ +template +void ClustererDPL::updateTimeDependentParams(ProcessingContext& pc) +{ + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + static bool initOnceDone = false; + if (!initOnceDone) { // this params need to be queried only once + initOnceDone = true; + pc.inputs().get("cldict"); // just to trigger the finaliseCCDB + pc.inputs().get*>("alppar"); + pc.inputs().get*>("cluspar"); + mClusterer->setContinuousReadOut(o2::base::GRPGeomHelper::instance().getGRPECS()->isDetContinuousReadOut(N)); + // settings for the fired pixel overflow masking + const auto& alpParams = o2::itsmft::DPLAlpideParam::Instance(); + const auto& clParams = o2::itsmft::ClustererParam::Instance(); + if (clParams.maxBCDiffToMaskBias > 0 && clParams.maxBCDiffToSquashBias > 0) { + LOGP(fatal, "maxBCDiffToMaskBias = {} and maxBCDiffToSquashBias = {} cannot be set at the same time. Either set masking or squashing with a BCDiff > 0", clParams.maxBCDiffToMaskBias, clParams.maxBCDiffToSquashBias); + } + mClusterer->setDropHugeClusters(clParams.dropHugeClusters); + auto nbc = clParams.maxBCDiffToMaskBias; + nbc += mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); + mClusterer->setMaxBCSeparationToMask(nbc); + mClusterer->setMaxRowColDiffToMask(clParams.maxRowColDiffToMask); + // Squasher + int rofBC = mClusterer->isContinuousReadOut() ? alpParams.roFrameLengthInBC : (alpParams.roFrameLengthTrig / o2::constants::lhc::LHCBunchSpacingNS); // ROF length in BC + mClusterer->setMaxBCSeparationToSquash(rofBC + clParams.maxBCDiffToSquashBias); + int nROFsToSquash = 0; // squashing disabled if no reset due to maxSOTMUS>0. + if (clParams.maxSOTMUS > 0 && rofBC > 0) { + nROFsToSquash = 2 + int(clParams.maxSOTMUS / (rofBC * o2::constants::lhc::LHCBunchSpacingMUS)); // use squashing + } + mClusterer->setMaxROFDepthToSquash(nROFsToSquash); + if (mDoStaggering) { + if (mClusterer->isContinuousReadOut()) { + 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); + } + // we may have other params which need to be queried regularly +} + +///_______________________________________ +template +void ClustererDPL::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { + return; + } + if (matcher == ConcreteDataMatcher(Origin, "CLUSDICT", 0)) { + LOG(info) << "cluster dictionary updated" << (!mUseClusterDictionary ? " but its using is disabled" : ""); + if (mUseClusterDictionary) { + mClusterer->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(Origin, "ALPIDEPARAM", 0)) { + LOG(info) << "Alpide param updated"; + const auto& par = o2::itsmft::DPLAlpideParam::Instance(); + par.printKeyValues(); + return; + } + if (matcher == ConcreteDataMatcher(Origin, "CLUSPARAM", 0)) { + LOG(info) << "Cluster param updated"; + const auto& par = o2::itsmft::ClustererParam::Instance(); + par.printKeyValues(); + return; + } +} + +namespace +{ +template +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; + 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("cldict", Origin, "CLUSDICT", 0, Lifetime::Condition, ccdbParamSpec(Origin.as() + "/Calib/ClusterDictionary")); + inputs.emplace_back("cluspar", Origin, "CLUSPARAM", 0, Lifetime::Condition, ccdbParamSpec(Origin.as() + "/Config/ClustererParam")); + inputs.emplace_back("alppar", Origin, "ALPIDEPARAM", 0, Lifetime::Condition, ccdbParamSpec(Origin.as() + "/Config/AlpideParam")); + auto ggRequest = std::make_shared(false, // orbitResetTime + true, // GRPECS=true + false, // GRPLHCIF + false, // GRPMagField + false, // askMatLUT + o2::base::GRPGeomRequest::None, // geometry + inputs, + true); + std::vector outputs; + for (uint32_t iLayer = 0; iLayer < nLayers; ++iLayer) { + outputs.emplace_back(Origin, "COMPCLUSTERS", iLayer, Lifetime::Timeframe); + outputs.emplace_back(Origin, "PATTERNS", iLayer, Lifetime::Timeframe); + outputs.emplace_back(Origin, "CLUSTERSROF", iLayer, Lifetime::Timeframe); + if (useMC) { + outputs.emplace_back(Origin, "CLUSTERSMCTR", iLayer, Lifetime::Timeframe); + outputs.emplace_back(Origin, "CLUSTERSMC2ROF", iLayer, Lifetime::Timeframe); + } + } + return DataProcessorSpec{ + .name = (N == o2::detectors::DetID::ITS) ? "its-clusterer" : "mft-clusterer", + .inputs = inputs, + .outputs = outputs, + .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, bool doStag) +{ + return getClustererSpec(useMC, doStag); +} + +framework::DataProcessorSpec getMFTClustererSpec(bool useMC, bool doStag) +{ + 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 3c7a86fe173d6..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. // @@ -11,15 +11,17 @@ /// @file DigitReaderSpec.cxx +#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" @@ -39,25 +41,27 @@ namespace o2 namespace itsmft { -DigitReader::DigitReader(o2::detectors::DetID id, bool useMC, bool useCalib, bool triggerOut) +template +DigitReader::DigitReader(bool useMC, bool doStag, bool useCalib, bool triggerOut) : mUseMC(useMC), mDoStaggering(doStag), mUseCalib(useCalib), mTriggerOut(triggerOut), mDetNameLC(mDetName = ID.getName()), mDigTreeName("o2sim") { - assert(id == o2::detectors::DetID::ITS || id == o2::detectors::DetID::MFT); - mDetNameLC = mDetName = id.getName(); - mDigTreeName = "o2sim"; - mDigitBranchName = mDetName + mDigitBranchName; - mDigROFBranchName = mDetName + mDigROFBranchName; + mDigitROFBranchName = mDetName + mDigitROFBranchName; mCalibBranchName = mDetName + mCalibBranchName; - mDigtMCTruthBranchName = mDetName + mDigtMCTruthBranchName; - mDigtMC2ROFBranchName = mDetName + mDigtMC2ROFBranchName; - mTriggerOut = triggerOut; - mUseMC = useMC; - mUseCalib = useCalib; + mDigitMCTruthBranchName = mDetName + mDigitMCTruthBranchName; + std::transform(mDetNameLC.begin(), mDetNameLC.end(), mDetNameLC.begin(), ::tolower); + + if (mDoStaggering) { + mLayers = DPLAlpideParam::getNLayers(); + mDigits.resize(mLayers, nullptr); + mDigROFRec.resize(mLayers, nullptr); + mPLabels.resize(mLayers, nullptr); + } } -void DigitReader::init(InitContext& ic) +template +void DigitReader::init(InitContext& ic) { mFileName = o2::utils::Str::concat_string(o2::utils::Str::rectifyDirectory(ic.options().get("input-dir")), ic.options().get((mDetNameLC + "-digit-infile").c_str())); @@ -67,23 +71,23 @@ void DigitReader::init(InitContext& ic) connectTree(mFileName); } -void DigitReader::run(ProcessingContext& pc) +template +void DigitReader::run(ProcessingContext& pc) { const auto& tinfo = pc.services().get(); + const auto& alpideParam = o2::itsmft::DPLAlpideParam::Instance(); if (tinfo.globalRunNumberChanged && mUseIRFrames) { // new run is starting: 1st call // TODO: we have to find a way define CCDBInput for IRFrames mode only using DPL fetcher auto& ccdb = o2::ccdb::BasicCCDBManager::instance(); auto rlim = ccdb.getRunDuration(tinfo.runNumber); long ts = (rlim.first + rlim.second) / 2; - if (mOrigin == o2::header::gDataOriginITS) { + if constexpr (N == o2::detectors::DetID::ITS) { ccdb.getForTimeStamp>("ITS/Config/AlpideParam", ts); - const auto& alpideParam = o2::itsmft::DPLAlpideParam::Instance(); mROFBiasInBC = alpideParam.roFrameBiasInBC; mROFLengthInBC = alpideParam.roFrameLengthInBC; mNRUs = o2::itsmft::ChipMappingITS::getNRUs(); } else { ccdb.getForTimeStamp>("MFT/Config/AlpideParam", ts); - const auto& alpideParam = o2::itsmft::DPLAlpideParam::Instance(); mROFBiasInBC = alpideParam.roFrameBiasInBC; mROFLengthInBC = alpideParam.roFrameLengthInBC; mNRUs = o2::itsmft::ChipMappingMFT::getNRUs(); @@ -93,42 +97,42 @@ void DigitReader::run(ProcessingContext& pc) if (mUseIRFrames) { irFrames = pc.inputs().get>("driverInfo"); } - static o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - if (mUseMC && !plabels) { - mTree->SetBranchAddress(mDigtMCTruthBranchName.c_str(), &plabels); - } - auto ent = mTree->GetReadEntry(); + auto ent = mTree->GetReadEntry(); if (!mUseIRFrames) { ent++; assert(ent < mTree->GetEntries()); // this should not happen mTree->GetEntry(ent); - LOG(info) << mDetName << "DigitReader pushes " << mDigROFRec.size() << " ROFRecords, " << mDigits.size() << " digits at entry " << ent; - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", 0}, mDigROFRec); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, mDigits); + 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) { + auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", iLayer}); + mPLabels[iLayer]->copyandflatten(sharedlabels); + delete mPLabels[iLayer]; + mPLabels[iLayer] = nullptr; + // read dummy MC2ROF vector to keep writer/readers backward compatible + static std::vector dummyMC2ROF; + pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", iLayer}, dummyMC2ROF); + } + } if (mUseCalib) { - pc.outputs().snapshot(Output{mOrigin, "GBTCALIB", 0}, mCalib); + pc.outputs().snapshot(Output{Origin, "GBTCALIB", 0}, mCalib); } if (mTriggerOut) { std::vector dummyTrig; - pc.outputs().snapshot(Output{mOrigin, "PHYSTRIG", 0}, dummyTrig); - } - if (mUseMC) { - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", 0}); - plabels->copyandflatten(sharedlabels); - delete plabels; - plabels = nullptr; - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", 0}, mDigMC2ROFs); + pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, dummyTrig); } if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } } else { // need to select particulars IRs range, presumably from the same tree entry + // TODO implement for staggering 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 @@ -144,33 +148,33 @@ void DigitReader::run(ProcessingContext& pc) // do we need to read a new entry? if (ent > mTree->GetReadEntry()) { if (mUseMC) { - delete plabels; - plabels = nullptr; - mConstLabels.clear(); - mTree->SetBranchAddress(mDigtMCTruthBranchName.c_str(), &plabels); + delete mPLabels[0]; + mPLabels[0] = nullptr; + mConstLabels[0].clear(); + mTree->SetBranchAddress(mDigitMCTruthBranchName.c_str(), &mPLabels[0]); } mTree->GetEntry(ent); if (mUseMC) { - plabels->copyandflatten(mConstLabels); - delete plabels; - plabels = nullptr; + mPLabels[0]->copyandflatten(mConstLabels[0]); + delete mPLabels[0]; + mPLabels[0] = nullptr; } } std::vector rofOld2New; - rofOld2New.resize(mDigROFRec.size(), -1); + rofOld2New.resize(mDigROFRec[0]->size(), -1); - if (mDigROFRec.front().getBCData() <= irMax && (mDigROFRec.back().getBCData() + mROFLengthInBC - 1) >= irMin) { // there is an overlap - for (int irof = 0; irof < (int)mDigROFRec.size(); irof++) { - const auto& rof = mDigROFRec[irof]; + if (mDigROFRec[0]->front().getBCData() <= irMax && (mDigROFRec[0]->back().getBCData() + mROFLengthInBC - 1) >= irMin) { // there is an overlap + for (int irof = 0; irof < (int)mDigROFRec[0]->size(); irof++) { + const auto& rof = mDigROFRec[0]->at(irof); if (irfSel.check({rof.getBCData(), rof.getBCData() + mROFLengthInBC - 1}) != -1) { rofOld2New[irof] = (int)digROFRecSel.size(); LOGP(debug, "Adding selected ROF {}", rof.getBCData().asString()); digROFRecSel.push_back(rof); int offs = digitsSel.size(); digROFRecSel.back().setFirstEntry(offs); - std::copy(mDigits.begin() + rof.getFirstEntry(), mDigits.begin() + rof.getFirstEntry() + rof.getNEntries(), std::back_inserter(digitsSel)); + std::copy(mDigits[0]->begin() + rof.getFirstEntry(), mDigits[0]->begin() + rof.getFirstEntry() + rof.getNEntries(), std::back_inserter(digitsSel)); for (int id = 0; id < rof.getNEntries(); id++) { // copy MC info - digitLabelsSel.addElements(id + offs, mConstLabels.getLabels(id + rof.getFirstEntry())); + digitLabelsSel.addElements(id + offs, mConstLabels[0].getLabels(id + rof.getFirstEntry())); } if (mCalib.size() >= size_t((irof + 1) * mNRUs)) { std::copy(mCalib.begin() + irof * mNRUs, mCalib.begin() + (irof + 1) * mNRUs, std::back_inserter(calibSel)); @@ -178,46 +182,25 @@ void DigitReader::run(ProcessingContext& pc) } } } - if (mUseMC) { - digMC2ROFsSel = mDigMC2ROFs; - 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.back().getBCData() + mROFLengthInBC - 1 < irMax) { // need to check the next entry + if (mDigROFRec[0]->back().getBCData() + mROFLengthInBC - 1 < irMax) { // need to check the next entry ent++; continue; } break; // push collected data } } - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", 0}, digROFRecSel); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, digitsSel); + pc.outputs().snapshot(Output{Origin, "DIGITSROF", 0}, digROFRecSel); + pc.outputs().snapshot(Output{Origin, "DIGITS", 0}, digitsSel); if (mUseCalib) { - pc.outputs().snapshot(Output{mOrigin, "GBTCALIB", 0}, calibSel); + pc.outputs().snapshot(Output{Origin, "GBTCALIB", 0}, calibSel); } if (mTriggerOut) { std::vector dummyTrig; - pc.outputs().snapshot(Output{mOrigin, "PHYSTRIG", 0}, dummyTrig); + pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, dummyTrig); } if (mUseMC) { - auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", 0}); + auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", 0}); digitLabelsSel.flatten_to(sharedlabels); - pc.outputs().snapshot(Output{mOrigin, "DIGITSMC2ROF", 0}, digMC2ROFsSel); } if (!irFrames.size() || irFrames.back().isLast()) { @@ -227,77 +210,98 @@ void DigitReader::run(ProcessingContext& pc) } } -void DigitReader::connectTree(const std::string& filename) +template +void DigitReader::connectTree(const std::string& filename) { mTree.reset(nullptr); // in case it was already loaded mFile.reset(TFile::Open(filename.c_str())); assert(mFile && !mFile->IsZombie()); mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str())); assert(mTree); - - mTree->SetBranchAddress(mDigROFBranchName.c_str(), &mDigROFRecPtr); - mTree->SetBranchAddress(mDigitBranchName.c_str(), &mDigitsPtr); + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { + setBranchAddress(mDigitROFBranchName, mDigROFRec[iLayer], iLayer); + setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer); + if (mUseMC) { + if (!mTree->GetBranch(getBranchName(mDigitMCTruthBranchName, iLayer).c_str())) { + throw std::runtime_error("MC data requested but not found in the tree"); + } + if (!mPLabels[iLayer]) { + setBranchAddress(mDigitMCTruthBranchName, 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"; } -DataProcessorSpec getITSDigitReaderSpec(bool useMC, bool useCalib, bool useTriggers, std::string defname) +template +std::string DigitReader::getBranchName(const std::string& base, int index) { - std::vector outputSpec; - outputSpec.emplace_back("ITS", "DIGITS", 0, Lifetime::Timeframe); - outputSpec.emplace_back("ITS", "DIGITSROF", 0, Lifetime::Timeframe); - if (useCalib) { - outputSpec.emplace_back("ITS", "GBTCALIB", 0, Lifetime::Timeframe); + if (mDoStaggering) { + return base + "_" + std::to_string(index); } - if (useMC) { - outputSpec.emplace_back("ITS", "DIGITSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("ITS", "DIGITSMC2ROF", 0, Lifetime::Timeframe); + return base; +} + +template +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); } - if (useTriggers) { - outputSpec.emplace_back("ITS", "PHYSTRIG", 0, Lifetime::Timeframe); +} + +namespace +{ +template +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; + 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) { + outputs.emplace_back(Origin, "DIGITSMC2ROF", iLayer, Lifetime::Timeframe); + outputs.emplace_back(Origin, "DIGITSMCTR", iLayer, Lifetime::Timeframe); + } + } + if (useCalib) { + outputs.emplace_back(Origin, "GBTCALIB", 0, Lifetime::Timeframe); } + outputs.emplace_back(Origin, "PHYSTRIG", 0, Lifetime::Timeframe); + return outputs; +} +} // namespace + +DataProcessorSpec getITSDigitReaderSpec(bool useMC, bool doStag, bool useCalib, bool useTriggers, std::string defname) +{ return DataProcessorSpec{ - "its-digit-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC, useCalib)}, - Options{ + .name = "its-digit-reader", + .inputs = Inputs{}, + .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) { - std::vector outputSpec; - outputSpec.emplace_back("MFT", "DIGITS", 0, Lifetime::Timeframe); - outputSpec.emplace_back("MFT", "DIGITSROF", 0, Lifetime::Timeframe); - if (useCalib) { - outputSpec.emplace_back("MFT", "GBTCALIB", 0, Lifetime::Timeframe); - } - if (useMC) { - outputSpec.emplace_back("MFT", "DIGITSMCTR", 0, Lifetime::Timeframe); - outputSpec.emplace_back("MFT", "DIGITSMC2ROF", 0, Lifetime::Timeframe); - } - if (useTriggers) { - outputSpec.emplace_back("MFT", "PHYSTRIG", 0, Lifetime::Timeframe); - } return DataProcessorSpec{ - "mft-digit-reader", - Inputs{}, - outputSpec, - AlgorithmSpec{adaptFromTask(useMC, useCalib)}, - Options{ + .name = "mft-digit-reader", + .inputs = Inputs{}, + .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 3a06d106ceb1f..944432196881e 100644 --- a/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/DigitWriterSpec.cxx @@ -12,6 +12,9 @@ /// @brief Processor spec for a ROOT file writer for ITSMFT digits #include "ITSMFTWorkflow/DigitWriterSpec.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/DataRef.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "DPLUtils/MakeRootTreeWriterSpec.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/GBTCalibData.h" @@ -39,14 +42,24 @@ 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) +template +DataProcessorSpec getDigitWriterSpec(bool mctruth, bool doStag, bool dec, bool calib) { - std::string detStr = o2::detectors::DetID::getName(detId); + static constexpr o2::header::DataOrigin Origin{N == o2::detectors::DetID::ITS ? o2::header::gDataOriginITS : o2::header::gDataOriginMFT}; + 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 logger = [](std::vector const& inDigits) { - LOG(info) << "RECEIVED DIGITS SIZE " << inDigits.size(); + 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 @@ -71,9 +84,11 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea // 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, doStag, 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" << (doStag ? std::format(" FOR LAYER {}", layer) : "") << " WITH " << (*digitSizes)[layer] << " DIGITS IN " << (*rofSizes)[layer] << " ROFS"; o2::dataformats::IOMCTruthContainerView outputcontainer; auto ptr = &outputcontainer; @@ -83,35 +98,67 @@ DataProcessorSpec getDigitWriterSpec(bool mctruth, bool dec, bool calib, o2::hea br->ResetAddress(); }; + auto getIndex = [](DataRef const& ref) -> size_t { + auto const* dh = DataRefUtils::getHeader(ref); + return static_cast(dh->subSpecification); + }; + 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{"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{(detStr + "_digitsMCTR").c_str(), detOrig, "DIGITSMCTR", 0}, - (detStr + "DigitMCTruth").c_str(), - (mctruth ? 1 : 0), fillLabels}, - BranchDefinition>{InputSpec{(detStr + "_digitsMC2ROF").c_str(), detOrig, "DIGITSMC2ROF", 0}, - (detStr + "DigitMC2ROF").c_str(), - (mctruth ? 1 : 0)}, - BranchDefinition>{InputSpec{(detStr + "digits").c_str(), detOrig, "DIGITS", 0}, - (detStr + "Digit").c_str(), - logger}, - BranchDefinition>{InputSpec{(detStr + "calib").c_str(), detOrig, "GBTCALIB", 0}, - (detStr + "Calib").c_str(), - (calib ? 1 : 0)}, - BranchDefinition>{InputSpec{(detStr + "digitsROF").c_str(), detOrig, "DIGITSROF", 0}, - (detStr + "DigitROF").c_str()})(); + 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 + "_digitsMC2ROF", ConcreteDataTypeMatcher{Origin, "DIGITSMC2ROF"}}, + detStr + "DigitMC2ROF", "digit-mc2rof-branch", + (mctruth ? mLayers : 0), + getIndex, + getName}, + BranchDefinition>{InputSpec{detStr + "calib", ConcreteDataTypeMatcher{Origin, "GBTCALIB"}}, + detStr + "Calib", "digit-calib-branch", + (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, o2::header::gDataOriginITS, o2::detectors::DetID::ITS); + 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, o2::header::gDataOriginMFT, o2::detectors::DetID::MFT); + 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 4edbc10d5bfbd..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; @@ -29,25 +30,32 @@ namespace o2 namespace itsmft { -EntropyDecoderSpec::EntropyDecoderSpec(o2::header::DataOrigin orig, int verbosity, bool getDigits) - : mOrigin(orig), mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, orig == o2::header::gDataOriginITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT), 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(); @@ -55,105 +63,141 @@ 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) +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()))); - inputs.emplace_back(std::string("ctfdict") + nm, orig, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", orig.as()))); - inputs.emplace_back(std::string("trigoffset"), "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); + 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_"} + ID.getName(), Origin, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", Origin.as()))); + } + 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)}, - Options{ - {"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"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"}}}}; + 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 diff --git a/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/EntropyEncoderSpec.cxx index 4b35f6cc44e39..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; @@ -28,20 +28,30 @@ namespace o2 namespace itsmft { -EntropyEncoderSpec::EntropyEncoderSpec(o2::header::DataOrigin orig, bool selIR) - : mOrigin(orig), mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, orig == o2::header::gDataOriginITS ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT), 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(); @@ -50,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(); @@ -66,76 +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) +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("ctfdict", orig, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec(fmt::format("{}/Calib/CTFDictionaryTree", orig.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", 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)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + 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 6b0585e293db6..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" @@ -24,8 +25,9 @@ void customize(std::vector& workflowOptions) std::vector options{ ConfigParamSpec{"runmft", VariantType::Bool, false, {"source detector is MFT (default ITS)"}}, 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); } @@ -40,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)); + 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)); + 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/CTF/include/MCHCTF/CTFCoder.h b/Detectors/MUON/MCH/CTF/include/MCHCTF/CTFCoder.h index fc090c5c7e16d..a5f2af646c778 100644 --- a/Detectors/MUON/MCH/CTF/include/MCHCTF/CTFCoder.h +++ b/Detectors/MUON/MCH/CTF/include/MCHCTF/CTFCoder.h @@ -34,10 +34,10 @@ namespace o2 namespace mch { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::MCH) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::MCH, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/MUON/MCH/CTF/include/MCHCTF/EntropyDecoderSpec.h b/Detectors/MUON/MCH/CTF/include/MCHCTF/EntropyDecoderSpec.h index f28ca90e9a339..0c3534ff5cdd1 100644 --- a/Detectors/MUON/MCH/CTF/include/MCHCTF/EntropyDecoderSpec.h +++ b/Detectors/MUON/MCH/CTF/include/MCHCTF/EntropyDecoderSpec.h @@ -22,7 +22,7 @@ namespace o2 namespace mch { /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, const char* specName, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, const char* specName, unsigned int sspec, const std::string& ctfdictOpt = "none"); } // namespace mch } // namespace o2 diff --git a/Detectors/MUON/MCH/CTF/src/EntropyDecoderSpec.cxx b/Detectors/MUON/MCH/CTF/src/EntropyDecoderSpec.cxx index 9ec13fed85690..653120bd9b630 100644 --- a/Detectors/MUON/MCH/CTF/src/EntropyDecoderSpec.cxx +++ b/Detectors/MUON/MCH/CTF/src/EntropyDecoderSpec.cxx @@ -27,11 +27,10 @@ namespace o2 { namespace mch { - class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +42,7 @@ class EntropyDecoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -91,7 +90,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, const char* specName, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, const char* specName, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"rofs"}, "MCH", "DIGITROFS", 0, Lifetime::Timeframe}, @@ -101,17 +100,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, const char* specName, uns std::vector inputs; inputs.emplace_back("ctf_MCH", "MCH", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_MCH", "MCH", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MCH/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_MCH", "MCH", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MCH/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ specName, inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace mch } // namespace o2 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 - -using namespace o2::mch::contour; - -namespace o2 -{ -namespace mch -{ -namespace mapping -{ - -std::string svgCathodeSegmentationDefaultStyle() -{ - return R"( -.pads { - fill: #EEEEEE; - stroke-width: 0.025px; - stroke: #AAAAAA; -} -.padchannels { - font-size: 0.4px; - font-family: arial; - fill: blue; - text-anchor: middle; -} -.dualsampas { - fill:none; - stroke-width: 0.025px; - stroke: #333333; -} -.detectionelements { - fill:none; - stroke-width:0.025px; - stroke: #000000; -} -.testpoints { - fill:red; - stroke-width:0.025px; - stroke: black; - opacity: 0.5; -} -)"; -} - -void svgCathodeSegmentation(const CathodeSegmentation& seg, SVGWriter& w, bool showdes, bool showdualsampas, bool showpads, - bool showpadchannels) -{ - std::vector> dualSampaContours = getDualSampaContours(seg); - std::vector>> dualSampaPads = getPadPolygons(seg); - std::vector> dualSampaPadChannels = getPadChannels(seg); - - if (dualSampaPadChannels.size() != dualSampaPads.size()) { - throw std::runtime_error("gouze"); - } - - auto deContour = getEnvelop(seg); - auto box = getBBox(seg); - - if (showpads) { - w.svgGroupStart("pads"); - for (auto& dsp : dualSampaPads) { - for (auto& p : dsp) { - w.polygon(p); - } - } - w.svgGroupEnd(); - } - - if (showpadchannels) { - w.svgGroupStart("padchannels"); - for (auto i = 0; i < dualSampaPads.size(); ++i) { - auto& dsp = dualSampaPads[i]; - auto& dspch = dualSampaPadChannels[i]; - for (auto j = 0; j < dsp.size(); j++) { - auto bbox = getBBox(dsp[j]); - w.text(std::to_string(dspch[j]), bbox.xcenter(), - bbox.ymax() - 0.05 * bbox.height()); // SVG text y position is the bottom of the text - } - } - w.svgGroupEnd(); - } - - if (showdualsampas) { - w.svgGroupStart("dualsampas"); - for (auto& dsp : dualSampaContours) { - w.contour(dsp); - } - w.svgGroupEnd(); - } - - if (showdes) { - w.svgGroupStart("detectionelements"); - w.contour(deContour); - } -} - -} // namespace mapping -} // namespace mch -} // namespace o2 diff --git a/Detectors/MUON/MCH/ROFFiltering/include/MCHROFFiltering/TrackableFilter.h b/Detectors/MUON/MCH/ROFFiltering/include/MCHROFFiltering/TrackableFilter.h index c1f11f2d40e5c..68efedfc4b89d 100644 --- a/Detectors/MUON/MCH/ROFFiltering/include/MCHROFFiltering/TrackableFilter.h +++ b/Detectors/MUON/MCH/ROFFiltering/include/MCHROFFiltering/TrackableFilter.h @@ -33,7 +33,6 @@ namespace o2::mch * * @tparam : the type of the items pointed to by the ROFRecords */ - template 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/Raw/Encoder/Payload/RefBufferCRUBare.cxx b/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferCRUBare.cxx deleted file mode 100644 index 52e4581da1a71..0000000000000 --- a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferCRUBare.cxx +++ /dev/null @@ -1,938 +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 "RefBuffers.h" -#include -#include "MCHRawCommon/DataFormats.h" - -extern std::array REF_BUFFER_CRU_BARE_CHARGESUM; -template <> -gsl::span REF_BUFFER_CRU() -{ - return gsl::span(reinterpret_cast(&REF_BUFFER_CRU_BARE_CHARGESUM[0]), REF_BUFFER_CRU_BARE_CHARGESUM.size()); -} -std::array REF_BUFFER_CRU_BARE_CHARGESUM = { - // clang-format off -0x04, 0x40, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0xB0, 0x12, 0xB0, 0x12, -0x00, 0x00, 0x0D, 0x10, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA9, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA9, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA8, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xA8, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, -0x1B, 0x01, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x01, 0x0D, 0x10, -0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, 0x03, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x04, 0x40, 0x00, 0x00, 0x1E, 0x01, 0x00, 0x00, 0x40, 0x0A, 0x40, 0x0A, -0x00, 0x00, 0x0F, 0x00, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAE, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAE, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAE, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF3, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA2, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA2, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, -0x1E, 0x01, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x01, 0x0F, 0x00, -0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, 0x03, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x04, 0x40, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0x10, 0x0D, 0x10, 0x0D, -0x07, 0x00, 0x0D, 0x10, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAB, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAB, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA9, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x00, 0x00, -0x1B, 0x01, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x07, 0x01, 0x0D, 0x10, -0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, 0x03, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - - // clang-format on -}; diff --git a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferCRUUserLogic.cxx b/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferCRUUserLogic.cxx deleted file mode 100644 index 3c3781460f4d1..0000000000000 --- a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferCRUUserLogic.cxx +++ /dev/null @@ -1,67 +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 "RefBuffers.h" -#include -#include "MCHRawCommon/DataFormats.h" - -extern std::array REF_BUFFER_CRU_USERLOGIC_CHARGESUM; -template <> -gsl::span REF_BUFFER_CRU() -{ - return gsl::span(reinterpret_cast(&REF_BUFFER_CRU_USERLOGIC_CHARGESUM[0]), REF_BUFFER_CRU_USERLOGIC_CHARGESUM.size()); -} -std::array REF_BUFFER_CRU_USERLOGIC_CHARGESUM = { - // clang-format off -0x04, 0x40, 0x00, 0x00, 0x1E, 0x01, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, -0x0F, 0x00, 0x0F, 0x00, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0xF0, 0x40, 0x55, 0x55, 0xA1, 0x00, -0x03, 0x12, 0x00, 0xE3, 0x46, 0x00, 0xA0, 0x00, 0x01, 0x60, 0xD0, 0x00, -0x00, 0x58, 0xA2, 0x00, 0x04, 0x40, 0xBB, 0x11, 0x00, 0x01, 0xA0, 0x00, -0x18, 0x14, 0x02, 0x40, 0x90, 0x04, 0xA0, 0x00, 0x70, 0x6F, 0x04, 0x40, -0x00, 0x18, 0xA0, 0x00, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, -0xED, 0xDE, 0xED, 0xFE, 0xED, 0xDE, 0xED, 0xFE, 0x04, 0x40, 0x00, 0x00, -0x1E, 0x01, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0x01, 0x0F, 0x00, -0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, 0x03, 0x08, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x04, 0x40, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, -0x0F, 0x00, 0x0D, 0x10, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x13, 0x01, 0xF0, 0x40, 0x55, 0x55, 0x81, 0x38, -0x1A, 0x12, 0x80, 0xE0, 0x46, 0x00, 0x80, 0x38, 0x01, 0x60, 0xA0, 0x00, -0x00, 0x4D, 0x82, 0x38, 0x04, 0x60, 0xB8, 0x11, 0x00, 0x01, 0x80, 0x38, -0x18, 0x50, 0x00, 0x80, 0x90, 0x04, 0x80, 0x38, 0x28, 0x6E, 0x04, 0x40, -0x00, 0x18, 0x80, 0x38, 0x1E, 0x00, 0x50, 0x21, 0x01, 0x38, 0x82, 0x38, -0x1B, 0x01, 0x10, 0x00, 0x06, 0x28, 0x80, 0x38, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x80, 0x38, 0xED, 0xDE, 0xED, 0xFE, 0xED, 0xDE, 0xED, 0xFE, -0x13, 0x01, 0xF0, 0x40, 0x55, 0x55, 0x01, 0x04, 0x03, 0x12, 0x40, 0xF6, -0x46, 0x00, 0x00, 0x04, 0x01, 0x60, 0x40, 0x1A, 0x00, 0x54, 0x02, 0x04, -0x04, 0xD0, 0xBD, 0x11, 0x00, 0x01, 0x00, 0x04, 0x18, 0xB8, 0x06, 0x00, -0x96, 0x04, 0x00, 0x04, 0x84, 0x6F, 0x04, 0x40, 0x00, 0x18, 0x00, 0x04, -0xB8, 0x01, 0xF0, 0x20, 0x01, 0x94, 0x03, 0x04, 0x1B, 0x01, 0x10, 0x00, -0x06, 0xC2, 0x01, 0x04, 0x00, 0x00, 0x48, 0x00, 0xE9, 0x1B, 0x01, 0x04, -0x00, 0x04, 0x80, 0x01, 0x73, 0x00, 0x00, 0x04, 0x48, 0x12, 0x50, 0xEA, -0x46, 0x00, 0x00, 0x04, 0x01, 0x60, 0x40, 0x1A, 0x00, 0x00, 0x00, 0x04, -0x04, 0x40, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, -0x0F, 0x01, 0x0D, 0x10, 0x39, 0x30, 0x00, 0x00, 0x39, 0x30, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x02, 0xA6, 0x02, -0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00 - // clang-format on -}; diff --git a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTBare.cxx b/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTBare.cxx deleted file mode 100644 index 89b1602cb0489..0000000000000 --- a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTBare.cxx +++ /dev/null @@ -1,239 +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 "RefBuffers.h" -#include -#include "MCHRawCommon/DataFormats.h" - -extern std::array REF_BUFFER_GBT_BARE_CHARGESUM; -template <> -gsl::span REF_BUFFER_GBT() -{ - return gsl::span(reinterpret_cast(&REF_BUFFER_GBT_BARE_CHARGESUM[0]), REF_BUFFER_GBT_BARE_CHARGESUM.size()); -} -std::array REF_BUFFER_GBT_BARE_CHARGESUM = { - // clang-format off -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x69, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xEB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x7D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xEB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xAB, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xA8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0xE8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xE8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAB, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x28, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, -0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00 - // clang-format on -}; diff --git a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTUserLogic.cxx b/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTUserLogic.cxx deleted file mode 100644 index 9487037328ad2..0000000000000 --- a/Detectors/MUON/MCH/Raw/Encoder/Payload/RefBufferGBTUserLogic.cxx +++ /dev/null @@ -1,34 +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 "RefBuffers.h" -#include -#include "MCHRawCommon/DataFormats.h" - -extern std::array REF_BUFFER_GBT_USERLOGIC_CHARGESUM; -template <> -gsl::span REF_BUFFER_GBT() -{ - return gsl::span(reinterpret_cast(&REF_BUFFER_GBT_USERLOGIC_CHARGESUM[0]), REF_BUFFER_GBT_USERLOGIC_CHARGESUM.size()); -} -std::array REF_BUFFER_GBT_USERLOGIC_CHARGESUM = { - // clang-format off -0x13, 0x01, 0xF0, 0x40, 0x55, 0x55, 0x01, 0x58, 0x0C, 0x12, 0x00, 0xA0, -0x50, 0x03, 0x00, 0x58, 0x01, 0x30, 0xA0, 0x00, 0x00, 0x5B, 0x02, 0x58, -0x04, 0xC0, 0x2F, 0xD4, 0x00, 0x01, 0x00, 0x58, 0x0C, 0x80, 0x02, 0x00, -0x00, 0x00, 0x00, 0x58, 0x13, 0x01, 0xF0, 0x40, 0x55, 0x55, 0x61, 0x58, -0x19, 0x12, 0x60, 0xAD, 0x50, 0x03, 0x60, 0x58, 0x01, 0x30, 0xD0, 0x00, -0x00, 0x09, 0x62, 0x58, 0x04, 0x5C, 0x28, 0xD4, 0x00, 0x01, 0x60, 0x58, -0x0C, 0x14, 0x02, 0x40, 0x82, 0x04, 0x60, 0x58, 0xF7, 0x0B, 0x35, 0x40, -0x00, 0x0C, 0x60, 0x58, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x58 - - // clang-format on -}; 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/MUON/MCH/Workflow/src/entropy-encoder-workflow.cxx b/Detectors/MUON/MCH/Workflow/src/entropy-encoder-workflow.cxx index 058202dfb802b..c66365922233e 100644 --- a/Detectors/MUON/MCH/Workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/MUON/MCH/Workflow/src/entropy-encoder-workflow.cxx @@ -27,11 +27,10 @@ namespace o2 { namespace mch { - class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -44,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -85,12 +84,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(const char* specName, bool selIR) +DataProcessorSpec getEntropyEncoderSpec(const char* specName, bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("rofs", "MCH", "DIGITROFS", 0, Lifetime::Timeframe); inputs.emplace_back("digits", "MCH", "DIGITS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "MCH", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MCH/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "MCH", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MCH/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -99,14 +101,12 @@ DataProcessorSpec getEntropyEncoderSpec(const char* specName, bool selIR) inputs, Outputs{{"MCH", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "MCH", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace mch } // namespace o2 @@ -118,6 +118,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -133,6 +134,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); bool selIR = cfgc.options().get("select-ir-frames"); - wf.emplace_back(o2::mch::getEntropyEncoderSpec("mch-entropy-encoder", selIR)); + wf.emplace_back(o2::mch::getEntropyEncoderSpec("mch-entropy-encoder", selIR, cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/MUON/MID/CTF/include/MIDCTF/CTFCoder.h b/Detectors/MUON/MID/CTF/include/MIDCTF/CTFCoder.h index 3071b65db47b1..0a6ee12316921 100644 --- a/Detectors/MUON/MID/CTF/include/MIDCTF/CTFCoder.h +++ b/Detectors/MUON/MID/CTF/include/MIDCTF/CTFCoder.h @@ -34,10 +34,10 @@ namespace o2 namespace mid { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::MID) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::MID, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/MUON/MID/Calibration/macros/build_rejectlist.C b/Detectors/MUON/MID/Calibration/macros/build_rejectlist.C index 5cec2c611bcf8..06aca991be338 100644 --- a/Detectors/MUON/MID/Calibration/macros/build_rejectlist.C +++ b/Detectors/MUON/MID/Calibration/macros/build_rejectlist.C @@ -28,7 +28,6 @@ #include "TGraph.h" #include "TTimeStamp.h" #include "CCDB/CcdbApi.h" -#include "DataFormatsParameters/GRPECSObject.h" #include "DetectorsCommonDataFormats/DetID.h" #include "DataFormatsMID/ColumnData.h" #include "MIDBase/ColumnDataHandler.h" @@ -111,12 +110,18 @@ std::string timeRangeToString(long start, long end) std::vector findObjectsMDInPeriod(long start, long end, const o2::ccdb::CcdbApi& api, const char* path) { std::vector mds; - auto out = api.list(path, false, "application/json", getTSMS(end), getTSMS(start)); + long creationDelayMS = 300000; // The objects can be created up to 5 minutes after the end of run + auto out = api.list(path, false, "application/json", getTSMS(end) + creationDelayMS, getTSMS(start)); rapidjson::Document doc; doc.Parse(out.c_str()); for (auto& obj : doc["objects"].GetArray()) { MDStruct md; md.start = obj["validFrom"].GetInt64(); + if (getTSMS(end) < getTSMS(md.start)) { + // Since we query on the creation time, adding a delay + // we need to cross-check here that we are within the run + continue; + } md.end = obj["validUntil"].GetInt64(); md.runNumber = std::atoi(obj["RunNumber"].GetString()); md.runType = obj["RunType"].GetString(); diff --git a/Detectors/MUON/MID/Filtering/test/bench_Filter.cxx b/Detectors/MUON/MID/Filtering/test/bench_Filter.cxx deleted file mode 100644 index a54ea9c1733a8..0000000000000 --- a/Detectors/MUON/MID/Filtering/test/bench_Filter.cxx +++ /dev/null @@ -1,98 +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 MID/Tracking/test/bench_Tracker.cxx -/// \brief Benchmark tracker device for MID -/// \author Diego Stocco -/// \date 17 March 2018 - -#include "benchmark/benchmark.h" -#include -#include "DataFormatsMID/Cluster.h" -#include "DataFormatsMID/Track.h" -#include "MIDBase/HitFinder.h" -#include "MIDBase/Mapping.h" -#include "MIDBase/MpArea.h" -#include "MIDTestingSimTools/TrackGenerator.h" -#include "MIDTracking/Tracker.h" - -std::vector generateTestData(int nTracks, o2::mid::TrackGenerator& trackGen, - const o2::mid::HitFinder& hitFinder, const o2::mid::Mapping& mapping) -{ - o2::mid::Mapping::MpStripIndex stripIndex; - o2::mid::MpArea area; - std::vector clusters; - o2::mid::Cluster cl; - std::vector tracks = trackGen.generate(nTracks); - for (auto& track : tracks) { - for (int ich = 0; ich < 4; ++ich) { - auto hits = hitFinder.getLocalPositions(track, ich); - bool isFired = false; - for (auto& hit : hits) { - int deId = hit.deId; - float xPos = hit.xCoor; - float yPos = hit.yCoor; - stripIndex = mapping.stripByPosition(xPos, yPos, 0, deId, false); - if (!stripIndex.isValid()) { - continue; - } - cl.deId = deId; - area = mapping.stripByLocation(stripIndex.strip, 0, stripIndex.line, stripIndex.column, deId); - cl.yCoor = area.getCenterY(); - cl.yErr = area.getHalfSizeY() / std::sqrt(3.); - stripIndex = mapping.stripByPosition(xPos, yPos, 1, deId, false); - area = mapping.stripByLocation(stripIndex.strip, 1, stripIndex.line, stripIndex.column, deId); - cl.xCoor = area.getCenterX(); - cl.xErr = area.getHalfSizeX() / std::sqrt(3.); - clusters.push_back(cl); - } // loop on fired pos - } // loop on chambers - } // loop on tracks - return clusters; -} - -static void BM_TRACKER(benchmark::State& state) -{ - o2::mid::GeometryTransformer geoTrans = o2::mid::createDefaultTransformer(); - o2::mid::TrackGenerator trackGen; - o2::mid::HitFinder hitFinder(geoTrans); - o2::mid::Mapping mapping; - o2::mid::Tracker tracker(geoTrans); - - int nTracksPerEvent = state.range(0); - tracker.init((state.range(1) == 1)); - double num{0}; - - std::vector inputData; - - for (auto _ : state) { - state.PauseTiming(); - inputData = generateTestData(nTracksPerEvent, trackGen, hitFinder, mapping); - state.ResumeTiming(); - tracker.process(inputData); - ++num; - } - - state.counters["num"] = benchmark::Counter(num, benchmark::Counter::kIsRate); -} - -static void CustomArguments(benchmark::internal::Benchmark* bench) -{ - for (int itrack = 1; itrack <= 8; ++itrack) { - for (int imethod = 0; imethod < 2; ++imethod) { - bench->Args({itrack, imethod}); - } - } -} - -BENCHMARK(BM_TRACKER)->Apply(CustomArguments)->Unit(benchmark::kNanosecond); - -BENCHMARK_MAIN(); diff --git a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/ColumnDataSpecsUtils.h b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/ColumnDataSpecsUtils.h index d8ec401e6a473..5f0fc43e1afca 100644 --- a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/ColumnDataSpecsUtils.h +++ b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/ColumnDataSpecsUtils.h @@ -42,21 +42,28 @@ namespace mid namespace specs { -/// Returns the input specs for MID Column Data and corresponding ROFs and labels +/// Returns the input specs for MID Column Data and corresponding ROFs and labels for EventType Standard /// \param dataBind Data binding name /// \param dataDesc Input data description /// \param useMC Builds output specs for labels /// \return Vector of input specs -std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, bool useMC); +std::vector buildStandardInputSpecs(std::string_view dataBind, std::string_view dataDesc, bool useMC); -/// Returns the input specs for MID Column Data and corresponding ROFs and labels +/// Returns the input specs for MID Column Data and corresponding ROFs and labels for EventType Standard /// \param dataBind Data binding name /// \param dataDesc Input data description /// \param rofDesc Input ROF record description /// \param labelsDesc Input MC labels description /// \param useMC Builds output specs for labels /// \return Vector of input specs -std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc, std::string_view labelsDesc, bool useMC); +std::vector buildStandardInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc, std::string_view labelsDesc, bool useMC); + +/// Returns the input specs for MID Column Data and corresponding ROFs and labels for all three EventTypes +/// \param dataBind Data binding name +/// \param dataDesc Input data description +/// \param rofDesc Input ROF record description +/// \return Vector of input specs +std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc); /// Returns the output specs for the different event types /// \param bind Binding name @@ -71,22 +78,14 @@ std::vector buildOutputSpecs(std::string_view bind, std:: /// \return Vector of Output specs std::vector buildStandardOutputSpecs(std::string_view dataBind, std::string_view dataDesc, bool useMC); -/// Returns the inputs for the different event types +/// Returns the input matching a specific binding /// \param pc Processing context /// \param bind Binding name /// \return Array of spans template -std::array, NEvTypes> getInput(framework::ProcessingContext& pc, std::string_view bind) +gsl::span getInput(framework::ProcessingContext& pc, std::string_view bind, int subSpec = -1) { - std::array, 3> data; - for (auto const& inputRef : framework::InputRecordWalker(pc.inputs())) { - auto const* dh = framework::DataRefUtils::getHeader(inputRef); - auto subSpecIdx = static_cast(dh->subSpecification); - if (framework::DataRefUtils::match(inputRef, bind.data())) { - data[subSpecIdx] = pc.inputs().get>(inputRef); - } - } - return data; + return pc.inputs().get>(fmt::format("{}{}", bind.data(), subSpec >= 0 ? fmt::format("_{}", subSpec) : "")); } /// Gets the outputs @@ -94,7 +93,7 @@ std::array, NEvTypes> getInput(framework::ProcessingContext& /// \return vector of outputs std::vector buildOutputs(std::vector outputSpecs); -/// Returns the array of Column Data +/// Returns the array of Column Data for all three EventTypes /// \param pc Processing context /// \param dataBind Data binding name /// \return Array of Column Data spans @@ -107,7 +106,7 @@ std::array, NEvTypes> getData(framework::ProcessingC /// \return Span of ColumnData gsl::span getData(framework::ProcessingContext& pc, std::string_view dataBind, EventType eventType); -/// Returns the array of ROF records +/// Returns the array of ROF records for all three EventTypes /// \param pc Processing context /// \param dataBind Data binding name /// \return Array of ROF Records spans @@ -124,7 +123,7 @@ gsl::span getRofs(framework::ProcessingContext& pc, std::string /// \param pc Processing context /// \param dataBind Data binding name /// \return Pointer to MC labels -std::unique_ptr> getLabels(framework::ProcessingContext& pc, std::string_view dataBind); +std::unique_ptr> getLabels(framework::ProcessingContext& pc, std::string_view dataBind, EventType eventType = EventType::Standard); } // namespace specs } // namespace mid diff --git a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyDecoderSpec.h b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyDecoderSpec.h index 301db519b9a5f..8f466ac8b7a54 100644 --- a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyDecoderSpec.h +++ b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace mid class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace mid } // namespace o2 diff --git a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyEncoderSpec.h b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyEncoderSpec.h index e90c96e6ac8fe..20858ca6dfc07 100644 --- a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyEncoderSpec.h +++ b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/EntropyEncoderSpec.h @@ -29,7 +29,7 @@ namespace mid class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace mid } // namespace o2 diff --git a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/RawAggregatorSpec.h b/Detectors/MUON/MID/Workflow/include/MIDWorkflow/RawAggregatorSpec.h deleted file mode 100644 index b5a6b33530c8f..0000000000000 --- a/Detectors/MUON/MID/Workflow/include/MIDWorkflow/RawAggregatorSpec.h +++ /dev/null @@ -1,30 +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 MIDWorkflow/RawAggregatorSpec.h -/// \brief Data processor spec for MID raw data aggregator devices -/// \author Diego Stocco -/// \date 26 February 2020 - -#ifndef O2_MID_RAWAGGREGATORSPEC_H -#define O2_MID_RAWAGGREGATORSPEC_H - -#include "Framework/DataProcessorSpec.h" - -namespace o2 -{ -namespace mid -{ -framework::DataProcessorSpec getRawAggregatorSpec(); -} // namespace mid -} // namespace o2 - -#endif //O2_MID_RAWAGGREGATORSPEC_H diff --git a/Detectors/MUON/MID/Workflow/src/CalibDataProcessorSpec.cxx b/Detectors/MUON/MID/Workflow/src/CalibDataProcessorSpec.cxx index 1a6f2de9cd886..036178c04e867 100644 --- a/Detectors/MUON/MID/Workflow/src/CalibDataProcessorSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/CalibDataProcessorSpec.cxx @@ -61,19 +61,9 @@ class CalibDataProcessorDPL std::array, 3> data; std::array, 3> dataRof; - std::vector filter = { - {"check_data", of::ConcreteDataTypeMatcher{header::gDataOriginMID, "DATA"}, of::Lifetime::Timeframe}, - {"check_rof", of::ConcreteDataTypeMatcher{header::gDataOriginMID, "DATAROF"}, of::Lifetime::Timeframe}, - }; - - for (auto const& inputRef : of::InputRecordWalker(pc.inputs(), filter)) { - auto const* dh = framework::DataRefUtils::getHeader(inputRef); - auto subSpecIdx = static_cast(dh->subSpecification); - if (of::DataRefUtils::match(inputRef, "mid_data")) { - data[subSpecIdx] = pc.inputs().get>(inputRef); - } else if (of::DataRefUtils::match(inputRef, "mid_data_rof")) { - dataRof[subSpecIdx] = pc.inputs().get>(inputRef); - } + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { + data[subSpec] = pc.inputs().get>(fmt::format("mid_data_{}", subSpec)); + dataRof[subSpec] = pc.inputs().get>(fmt::format("mid_data_rof_{}", subSpec)); } mNoise.clear(); @@ -151,8 +141,10 @@ class CalibDataProcessorDPL of::DataProcessorSpec getCalibDataProcessorSpec(const FEEIdConfig& feeIdConfig, const CrateMasks& crateMasks) { std::vector inputSpecs; - inputSpecs.emplace_back("mid_data", of::ConcreteDataTypeMatcher(header::gDataOriginMID, "DATA"), of::Lifetime::Timeframe); - inputSpecs.emplace_back("mid_data_rof", of::ConcreteDataTypeMatcher(header::gDataOriginMID, "DATAROF"), of::Lifetime::Timeframe); + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { + inputSpecs.emplace_back(fmt::format("mid_data_{}", subSpec), header::gDataOriginMID, "DATA", subSpec, of::Lifetime::Timeframe); + inputSpecs.emplace_back(fmt::format("mid_data_rof_{}", subSpec), header::gDataOriginMID, "DATAROF", subSpec, of::Lifetime::Timeframe); + } std::vector outputSpecs; outputSpecs.emplace_back(header::gDataOriginMID, "NOISE", 0); diff --git a/Detectors/MUON/MID/Workflow/src/ClusterizerSpec.cxx b/Detectors/MUON/MID/Workflow/src/ClusterizerSpec.cxx index c544ce19fcdea..bf0d9608a2119 100644 --- a/Detectors/MUON/MID/Workflow/src/ClusterizerSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/ClusterizerSpec.cxx @@ -132,7 +132,7 @@ framework::DataProcessorSpec getClusterizerSpec(bool isMC, std::string_view inDa if (isMC) { outputSpecs.emplace_back(of::OutputSpec{header::gDataOriginMID, "CLUSTERSLABELS"}); } - auto inputSpecs = specs::buildInputSpecs("mid_cluster_in", inDataDesc, inRofDesc, inLabelsDesc, isMC); + auto inputSpecs = specs::buildStandardInputSpecs("mid_cluster_in", inDataDesc, inRofDesc, inLabelsDesc, isMC); return of::DataProcessorSpec{ "MIDClusterizer", diff --git a/Detectors/MUON/MID/Workflow/src/ColumnDataSpecsUtils.cxx b/Detectors/MUON/MID/Workflow/src/ColumnDataSpecsUtils.cxx index e0d41cd8d91d2..b4884ad68ad15 100644 --- a/Detectors/MUON/MID/Workflow/src/ColumnDataSpecsUtils.cxx +++ b/Detectors/MUON/MID/Workflow/src/ColumnDataSpecsUtils.cxx @@ -83,14 +83,26 @@ std::string buildSelectors(std::string_view dataBind, std::string_view dataDesc, return selector; } -std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, bool useMC) +std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc) { - return buildInputSpecs(dataBind, dataDesc, getROFDescription(dataDesc), getLabelsDescription(dataDesc), useMC); + std::string selector; + for (size_t ievt = 0; ievt < NEvTypes; ++ievt) { + if (!selector.empty()) { + selector += ";"; + } + selector += buildSelectors(dataBind, dataDesc, rofDesc, "", false, ievt); + } + return framework::select(selector.c_str()); +} + +std::vector buildStandardInputSpecs(std::string_view dataBind, std::string_view dataDesc, bool useMC) +{ + return buildStandardInputSpecs(dataBind, dataDesc, getROFDescription(dataDesc), getLabelsDescription(dataDesc), useMC); } -std::vector buildInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc, std::string_view labelsDesc, bool useMC) +std::vector buildStandardInputSpecs(std::string_view dataBind, std::string_view dataDesc, std::string_view rofDesc, std::string_view labelsDesc, bool useMC) { - std::string selector = buildSelectors(dataBind, dataDesc, rofDesc, labelsDesc, useMC); + std::string selector = buildSelectors(dataBind, dataDesc, rofDesc, labelsDesc, useMC, 0); return framework::select(selector.c_str()); } @@ -134,29 +146,37 @@ std::vector buildOutputs(std::vector o std::array, NEvTypes> getData(framework::ProcessingContext& pc, std::string_view dataBind) { - return getInput(pc, dataBind); + std::array, 3> data; + for (size_t ievt = 0; ievt < NEvTypes; ++ievt) { + data[ievt] = getInput(pc, dataBind, ievt); + } + + return data; } gsl::span getData(framework::ProcessingContext& pc, std::string_view dataBind, EventType eventType) { - auto idx = static_cast(eventType); - return getData(pc, dataBind)[idx]; + return getInput(pc, dataBind.data(), static_cast(eventType)); } std::array, NEvTypes> getRofs(framework::ProcessingContext& pc, std::string_view dataBind) { - return getInput(pc, getROFBind(dataBind)); + std::array, 3> data; + for (size_t ievt = 0; ievt < NEvTypes; ++ievt) { + data[ievt] = getInput(pc, getROFBind(dataBind).data(), ievt); + } + + return data; } gsl::span getRofs(framework::ProcessingContext& pc, std::string_view dataBind, EventType eventType) { - auto idx = static_cast(eventType); - return getRofs(pc, dataBind)[idx]; + return getInput(pc, getROFBind(dataBind).data(), static_cast(eventType)); } -std::unique_ptr> getLabels(framework::ProcessingContext& pc, std::string_view dataBind) +std::unique_ptr> getLabels(framework::ProcessingContext& pc, std::string_view dataBind, EventType eventType) { - return pc.inputs().get*>(getLabelsBind(dataBind).data()); + return pc.inputs().get*>(fmt::format("{}_{}", getLabelsBind(dataBind).data(), static_cast(eventType))); } } // namespace specs diff --git a/Detectors/MUON/MID/Workflow/src/DecodedDataAggregatorSpec.cxx b/Detectors/MUON/MID/Workflow/src/DecodedDataAggregatorSpec.cxx index 192b4c52be9cc..54b1a458fec0f 100644 --- a/Detectors/MUON/MID/Workflow/src/DecodedDataAggregatorSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/DecodedDataAggregatorSpec.cxx @@ -58,7 +58,7 @@ class DecodedDataAggregatorDeviceDPL mAggregator.process(data, inROFRecords); mTimerAlgo += std::chrono::high_resolution_clock::now() - tAlgoStart; - for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < 3; ++subSpec) { + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { EventType evtType = static_cast(subSpec); pc.outputs().snapshot(of::Output{o2::header::gDataOriginMID, "DATA", subSpec}, mAggregator.getData(evtType)); pc.outputs().snapshot(of::Output{o2::header::gDataOriginMID, "DATAROF", subSpec}, mAggregator.getROFRecords(evtType)); @@ -79,7 +79,7 @@ framework::DataProcessorSpec getDecodedDataAggregatorSpec() { std::vector inputSpecs{of::InputSpec{"mid_decoded", header::gDataOriginMID, "DECODED"}, of::InputSpec{"mid_decoded_rof", header::gDataOriginMID, "DECODEDROF"}}; std::vector outputSpecs; - for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < 3; ++subSpec) { + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { outputSpecs.emplace_back(of::OutputSpec{header::gDataOriginMID, "DATA", subSpec}); outputSpecs.emplace_back(of::OutputSpec{header::gDataOriginMID, "DATAROF", subSpec}); } diff --git a/Detectors/MUON/MID/Workflow/src/DecodedDataDumpSpec.cxx b/Detectors/MUON/MID/Workflow/src/DecodedDataDumpSpec.cxx deleted file mode 100644 index 77d05a8b3374f..0000000000000 --- a/Detectors/MUON/MID/Workflow/src/DecodedDataDumpSpec.cxx +++ /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. - -/// \file MID/Workflow/src/RawDumpSpec.cxx -/// \brief Device to dump decoded raw data -/// \author Diego Stocco -/// \date 17 February 2022 - -#include "MIDWorkflow/RawDumpSpec.h" - -#include -#include "Framework/ConfigParamRegistry.h" -#include "Framework/ControlService.h" -#include "Framework/Logger.h" -#include "Framework/Task.h" -#include "fmt/format.h" -#include "DataFormatsMID/ROBoard.h" -#include "DataFormatsMID/ROFRecord.h" - -namespace o2 -{ -namespace mid -{ - -class RawDumpDeviceDPL -{ - public: - void init(o2::framework::InitContext& ic) - { - auto outFilename = ic.options().get("mid-dump-outfile"); - - if (!outFilename.empty()) { - mOutFile.open(outFilename.c_str()); - } - } - - void - run(o2::framework::ProcessingContext& pc) - { - - auto data = pc.inputs().get>("mid_decoded"); - auto dataROFs = pc.inputs().get>("mid_decoded_rof"); - std::stringstream ss; - for (auto& rof : dataROFs) { - ss << fmt::format("BCid: 0x{:x} Orbit: 0x{:x} EvtType: {:d}", rof.interactionRecord.bc, rof.interactionRecord.orbit, static_cast(rof.eventType)) << std::endl; - for (auto colIt = data.begin() + rof.firstEntry, end = data.begin() + rof.getEndIndex(); colIt != end; ++colIt) { - ss << *colIt << std::endl; - } - } - if (mOutFile.is_open()) { - mOutFile << ss.str(); - } else { - LOG(info) << ss.str(); - } - } - - private: - std::ofstream mOutFile; /// Output file -}; - -framework::DataProcessorSpec getRawDumpSpec() -{ - std::vector inputSpecs{ - o2::framework::InputSpec{"mid_decoded", header::gDataOriginMID, "DECODED", 0, o2::framework::Lifetime::Timeframe}, - o2::framework::InputSpec{"mid_decoded_rof", header::gDataOriginMID, "DECODEDROF", 0, o2::framework::Lifetime::Timeframe}}; - - return o2::framework::DataProcessorSpec{ - "MIDRawDataDumper", - {inputSpecs}, - {}, - o2::framework::AlgorithmSpec{o2::framework::adaptFromTask()}, - o2::framework::Options{{"mid-dump-outfile", o2::framework::VariantType::String, "", {"Dump output to file"}}}}; -} - -} // namespace mid -} // namespace o2 diff --git a/Detectors/MUON/MID/Workflow/src/EntropyDecoderSpec.cxx b/Detectors/MUON/MID/Workflow/src/EntropyDecoderSpec.cxx index 5a8df6f8e81cb..0f6dc8bbaa995 100644 --- a/Detectors/MUON/MID/Workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/EntropyDecoderSpec.cxx @@ -26,8 +26,7 @@ namespace o2 { namespace mid { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -84,7 +83,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs; for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { @@ -94,17 +93,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) outputs.emplace_back(OutputSpec{{"ctfrep"}, "MID", "CTFDECREP", 0, Lifetime::Timeframe}); std::vector inputs; inputs.emplace_back("ctf_MID", "MID", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_MID", "MID", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MID/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_MID", "MID", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MID/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "mid-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace mid } // namespace o2 diff --git a/Detectors/MUON/MID/Workflow/src/EntropyEncoderSpec.cxx b/Detectors/MUON/MID/Workflow/src/EntropyEncoderSpec.cxx index a472d6e28ff16..f8d9922db25fa 100644 --- a/Detectors/MUON/MID/Workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/EntropyEncoderSpec.cxx @@ -32,8 +32,7 @@ namespace o2 { namespace mid { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -57,26 +56,15 @@ void EntropyEncoderSpec::run(ProcessingContext& pc) mTimer.Start(false); mCTFCoder.updateTimeDependentParams(pc, true); CTFHelper::TFData tfData; - std::vector - filter = { - {"check", ConcreteDataTypeMatcher{header::gDataOriginMID, "DATA"}, Lifetime::Timeframe}, - {"check", ConcreteDataTypeMatcher{header::gDataOriginMID, "DATAROF"}, Lifetime::Timeframe}, - }; size_t insize = 0; - for (auto const& inputRef : InputRecordWalker(pc.inputs(), filter)) { - auto const* dh = framework::DataRefUtils::getHeader(inputRef); - if (dh->subSpecification >= NEvTypes) { - throw std::runtime_error(fmt::format("SubSpecification={} does not match EvenTypes for {}", dh->subSpecification, dh->dataDescription.as())); - } - if (DataRefUtils::match(inputRef, "cols")) { - tfData.colData[dh->subSpecification] = pc.inputs().get>(inputRef); - insize += tfData.colData[dh->subSpecification].size() * sizeof(o2::mid::ColumnData); - } - if (DataRefUtils::match(inputRef, "rofs")) { - tfData.rofData[dh->subSpecification] = pc.inputs().get>(inputRef); - insize += tfData.rofData[dh->subSpecification].size() * sizeof(o2::mid::ROFRecord); - } + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { + tfData.colData[subSpec] = pc.inputs().get>(fmt::format("cols_{}", subSpec)); + insize += tfData.colData[subSpec].size() * sizeof(o2::mid::ColumnData); + + tfData.rofData[subSpec] = pc.inputs().get>(fmt::format("rofs_{}", subSpec)); + insize += tfData.rofData[subSpec].size() * sizeof(o2::mid::ROFRecord); } + if (mSelIR) { mCTFCoder.setSelectedIRFrames(pc.inputs().get>("selIRFrames")); } @@ -100,12 +88,17 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; - inputs.emplace_back("rofs", ConcreteDataTypeMatcher(header::gDataOriginMID, "DATAROF"), Lifetime::Timeframe); - inputs.emplace_back("cols", ConcreteDataTypeMatcher(header::gDataOriginMID, "DATA"), Lifetime::Timeframe); - inputs.emplace_back("ctfdict", header::gDataOriginMID, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MID/Calib/CTFDictionaryTree")); + for (o2::header::DataHeader::SubSpecificationType subSpec = 0; subSpec < NEvTypes; ++subSpec) { + inputs.emplace_back(fmt::format("cols_{}", subSpec), header::gDataOriginMID, "DATA", subSpec, Lifetime::Timeframe); + inputs.emplace_back(fmt::format("rofs_{}", subSpec), header::gDataOriginMID, "DATAROF", subSpec, Lifetime::Timeframe); + } + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", header::gDataOriginMID, "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("MID/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -114,13 +107,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{header::gDataOriginMID, "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, header::gDataOriginMID, "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace mid } // namespace o2 diff --git a/Detectors/MUON/MID/Workflow/src/FilteringBCSpec.cxx b/Detectors/MUON/MID/Workflow/src/FilteringBCSpec.cxx index 2aadabeab0bed..2d697f4bc5b1d 100644 --- a/Detectors/MUON/MID/Workflow/src/FilteringBCSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/FilteringBCSpec.cxx @@ -100,7 +100,7 @@ class FilteringBCDeviceDPL of::DataProcessorSpec getFilteringBCSpec(bool useMC, std::string_view inDesc) { - auto inputSpecs = specs::buildInputSpecs("mid_filter_BC_in", inDesc, useMC); + auto inputSpecs = specs::buildStandardInputSpecs("mid_filter_BC_in", inDesc, useMC); auto ggRequest = std::make_shared(false, // orbitResetTime false, // GRPECS=true true, // GRPLHCIF diff --git a/Detectors/MUON/MID/Workflow/src/FilteringSpec.cxx b/Detectors/MUON/MID/Workflow/src/FilteringSpec.cxx index 0ccbbe237b9a5..6ef3424e1ec29 100644 --- a/Detectors/MUON/MID/Workflow/src/FilteringSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/FilteringSpec.cxx @@ -143,7 +143,7 @@ class FilteringDeviceDPL of::DataProcessorSpec getFilteringSpec(bool useMC, std::string_view inDesc, std::string_view outDesc) { - auto inputSpecs = specs::buildInputSpecs("mid_filter_in", inDesc, useMC); + auto inputSpecs = specs::buildStandardInputSpecs("mid_filter_in", inDesc, useMC); inputSpecs.emplace_back("mid_bad_channels", header::gDataOriginMID, "BAD_CHANNELS", 0, of::Lifetime::Condition, of::ccdbParamSpec("MID/Calib/BadChannels")); inputSpecs.emplace_back("mid_rejectlist", header::gDataOriginMID, "REJECTLIST", 0, of::Lifetime::Condition, of::ccdbParamSpec("MID/Calib/RejectList")); diff --git a/Detectors/MUON/MID/Workflow/src/MaskMakerSpec.cxx b/Detectors/MUON/MID/Workflow/src/MaskMakerSpec.cxx index 28d2ff953ea23..d4b63de7e5d3f 100644 --- a/Detectors/MUON/MID/Workflow/src/MaskMakerSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/MaskMakerSpec.cxx @@ -69,28 +69,10 @@ class MaskMakerDeviceDPL gsl::span calibData, fetData; gsl::span calibDataRof, fetDataRof; - std::vector filter = { - {"check_data", of::ConcreteDataTypeMatcher{header::gDataOriginMID, "DATA"}, of::Lifetime::Timeframe}, - {"check_rof", of::ConcreteDataTypeMatcher{header::gDataOriginMID, "DATAROF"}, of::Lifetime::Timeframe}, - }; - - for (auto const& inputRef : of::InputRecordWalker(pc.inputs(), filter)) { - auto const* dh = framework::DataRefUtils::getHeader(inputRef); - if (of::DataRefUtils::match(inputRef, "mid_data")) { - if (dh->subSpecification == 1) { - calibData = pc.inputs().get>(inputRef); - } else if (dh->subSpecification == 2) { - fetData = pc.inputs().get>(inputRef); - } - } - if (of::DataRefUtils::match(inputRef, "mid_data_rof")) { - if (dh->subSpecification == 1) { - calibDataRof = pc.inputs().get>(inputRef); - } else if (dh->subSpecification == 2) { - fetDataRof = pc.inputs().get>(inputRef); - } - } - } + calibData = pc.inputs().get>("mid_data_1"); + calibDataRof = pc.inputs().get>("mid_data_rof_1"); + fetData = pc.inputs().get>("mid_data_2"); + fetDataRof = pc.inputs().get>("mid_data_rof_2"); unsigned long nEvents = calibDataRof.size(); if (nEvents == 0) { @@ -145,8 +127,10 @@ class MaskMakerDeviceDPL framework::DataProcessorSpec getMaskMakerSpec(const FEEIdConfig& feeIdConfig, const CrateMasks& crateMasks) { std::vector inputSpecs; - inputSpecs.emplace_back("mid_data", of::ConcreteDataTypeMatcher(header::gDataOriginMID, "DATA"), of::Lifetime::Timeframe); - inputSpecs.emplace_back("mid_data_rof", of::ConcreteDataTypeMatcher(header::gDataOriginMID, "DATAROF"), of::Lifetime::Timeframe); + for (o2::header::DataHeader::SubSpecificationType subSpec = 1; subSpec < NEvTypes; ++subSpec) { + inputSpecs.emplace_back(fmt::format("mid_data_{}", subSpec), o2::header::gDataOriginMID, "DATA", subSpec, of::Lifetime::Timeframe); + inputSpecs.emplace_back(fmt::format("mid_data_rof_{}", subSpec), o2::header::gDataOriginMID, "DATAROF", subSpec, of::Lifetime::Timeframe); + } std::vector outputSpecs{ of::OutputSpec{header::gDataOriginMID, "MASKS", 1}, diff --git a/Detectors/MUON/MID/Workflow/src/TimingSpec.cxx b/Detectors/MUON/MID/Workflow/src/TimingSpec.cxx index 05f669ab76ba4..5a397de9a045f 100644 --- a/Detectors/MUON/MID/Workflow/src/TimingSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/TimingSpec.cxx @@ -71,7 +71,7 @@ class TimingDeviceDPL of::DataProcessorSpec getTimingSpec(int localToBC, std::string_view inRofDesc) { - auto inputSpecs = specs::buildInputSpecs("mid_timing_in", "", inRofDesc, "", false); + auto inputSpecs = specs::buildInputSpecs("mid_timing_in", "", inRofDesc); auto outputSpecs = specs::buildOutputSpecs("mid_timing_out", "TDATAROF"); return of::DataProcessorSpec{ diff --git a/Detectors/MUON/MID/Workflow/src/ZeroSuppressionSpec.cxx b/Detectors/MUON/MID/Workflow/src/ZeroSuppressionSpec.cxx index 5d89eee81c629..7298ad9e506e3 100644 --- a/Detectors/MUON/MID/Workflow/src/ZeroSuppressionSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/ZeroSuppressionSpec.cxx @@ -103,7 +103,7 @@ class ZeroSuppressionDeviceDPL framework::DataProcessorSpec getZeroSuppressionSpec(bool useMC, std::string_view dataDesc) { - auto inputSpecs = specs::buildInputSpecs("mid_zs_in", dataDesc, useMC); + auto inputSpecs = specs::buildStandardInputSpecs("mid_zs_in", dataDesc, useMC); auto outputSpecs = specs::buildStandardOutputSpecs("mid_zs_out", "DATA", useMC); return of::DataProcessorSpec{ diff --git a/Detectors/MUON/MID/Workflow/src/decoded-data-dump-workflow.cxx b/Detectors/MUON/MID/Workflow/src/decoded-data-dump-workflow.cxx deleted file mode 100644 index 036b63bc75338..0000000000000 --- a/Detectors/MUON/MID/Workflow/src/decoded-data-dump-workflow.cxx +++ /dev/null @@ -1,65 +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 MID/Workflow/src/raw-dump-workflow.cxx -/// \brief MID raw dump workflow -/// \author Diego Stocco -/// \date 17 February 2022 - -#include -#include -#include "Framework/Variant.h" -#include "Framework/ConfigParamSpec.h" -#include "MIDRaw/CrateMasks.h" -#include "MIDRaw/ElectronicsDelay.h" -#include "MIDRaw/FEEIdConfig.h" -#include "MIDWorkflow/RawDumpSpec.h" -#include "MIDWorkflow/RawDecoderSpec.h" - -using namespace o2::framework; - -// add workflow options, note that customization needs to be declared before -// including Framework/runDataProcessing -void customize(std::vector& workflowOptions) -{ - std::vector - options{ - {"feeId-config-file", VariantType::String, "", {"Filename with crate FEE ID correspondence"}}, - {"crate-masks-file", VariantType::String, "", {"Filename with crate masks"}}, - {"electronics-delay-file", VariantType::String, "", {"Filename with electronics delay"}}}; - workflowOptions.insert(workflowOptions.end(), options.begin(), options.end()); -} - -#include "Framework/runDataProcessing.h" - -WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) -{ - auto feeIdConfigFilename = cfgc.options().get("feeId-config-file"); - o2::mid::FEEIdConfig feeIdConfig; - if (!feeIdConfigFilename.empty()) { - feeIdConfig = o2::mid::FEEIdConfig(feeIdConfigFilename.c_str()); - } - auto crateMasksFilename = cfgc.options().get("crate-masks-file"); - o2::mid::CrateMasks crateMasks; - if (!crateMasksFilename.empty()) { - crateMasks = o2::mid::CrateMasks(crateMasksFilename.c_str()); - } - auto electronicsDelayFilename = cfgc.options().get("electronics-delay-file"); - o2::mid::ElectronicsDelay electronicsDelay; - if (!electronicsDelayFilename.empty()) { - electronicsDelay = o2::mid::readElectronicsDelay(electronicsDelayFilename.c_str()); - } - - WorkflowSpec specs; - specs.emplace_back(o2::mid::getRawDecoderSpec(true, feeIdConfig, crateMasks, electronicsDelay, false)); - specs.emplace_back(o2::mid::getRawDumpSpec()); - return specs; -} diff --git a/Detectors/MUON/MID/Workflow/src/entropy-encoder-workflow.cxx b/Detectors/MUON/MID/Workflow/src/entropy-encoder-workflow.cxx index 56c482c514e38..a109250894ce9 100644 --- a/Detectors/MUON/MID/Workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/MUON/MID/Workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::mid::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::mid::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/PHOS/calib/include/PHOSCalibWorkflow/TurnOnHistos.h b/Detectors/PHOS/calib/include/PHOSCalibWorkflow/TurnOnHistos.h index 4457da2e100ad..0814fe0da4547 100644 --- a/Detectors/PHOS/calib/include/PHOSCalibWorkflow/TurnOnHistos.h +++ b/Detectors/PHOS/calib/include/PHOSCalibWorkflow/TurnOnHistos.h @@ -76,7 +76,7 @@ class TurnOnHistos /// \param bitset with channels fired in event void fillFiredMap(const std::bitset& bs) { - for (short i = NCHANNELS; --i;) { + for (size_t i = 0; i < NCHANNELS; ++i) { if (bs[i]) { mGoodMap[i]++; } @@ -87,7 +87,7 @@ class TurnOnHistos /// \param bitset with channels fired in event void fillNoisyMap(const std::bitset& bs) { - for (short i = NCHANNELS; --i;) { + for (size_t i = 0; i < NCHANNELS; ++i) { if (bs[i]) { mNoisyMap[i]++; } 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/PHOSRunbyrunCalibrator.cxx b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx index baa20307b0fbd..63e51f06c0e64 100644 --- a/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx +++ b/Detectors/PHOS/calib/src/PHOSRunbyrunCalibrator.cxx @@ -127,11 +127,11 @@ bool PHOSRunbyrunSlot::checkCluster(const Cluster& clu) return false; } // First check BadMap - float posX, posZ; + float posX{0}, posZ{0}; clu.getLocalPosition(posX, posZ); - short absId; + short absId{0}; Geometry::relPosToAbsId(clu.module(), posX, posZ, absId); - if (!mBadMap->isChannelGood(absId)) { + if (mBadMap && absId >= 0 && !mBadMap->isChannelGood(absId)) { return false; } return (clu.getEnergy() > 0.3 && clu.getMultiplicity() > 1); 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/PHOS/calib/src/PHOSTurnonCalibrator.cxx b/Detectors/PHOS/calib/src/PHOSTurnonCalibrator.cxx index 5413b20f491b8..432090c280ff8 100644 --- a/Detectors/PHOS/calib/src/PHOSTurnonCalibrator.cxx +++ b/Detectors/PHOS/calib/src/PHOSTurnonCalibrator.cxx @@ -36,7 +36,7 @@ PHOSTurnonSlot::PHOSTurnonSlot(bool useCCDB) : mUseCCDB(useCCDB) PHOSTurnonSlot::PHOSTurnonSlot(const PHOSTurnonSlot& other) { mUseCCDB = other.mUseCCDB; - mRunStartTime = other.mUseCCDB; + mRunStartTime = other.mRunStartTime; mFiredTiles.reset(); mNoisyTiles.reset(); mTurnOnHistos = std::make_unique(); @@ -91,15 +91,17 @@ void PHOSTurnonSlot::scanClusters(const gsl::span& cells, const Trig for (int i = firstCellInEvent; i < lastCellInEvent; i++) { const Cell& c = cells[i]; if (c.getTRU()) { - mNoisyTiles.set(c.getTRUId() - Geometry::getTotalNCells() - 1); + auto channel = c.getTRUId() - Geometry::getTotalNCells() - 1; + if (channel >= 0) { + mNoisyTiles.set(channel); + } } } // Copy to have good and noisy map mFiredTiles.reset(); - char mod; - float x, z; - short ddl; + float x{0}, z{0}; + short ddl{0}; int firstCluInEvent = clutr.getFirstEntry(); int lastCluInEvent = firstCluInEvent + clutr.getNumberOfObjects(); for (int i = firstCluInEvent; i < lastCluInEvent; i++) { @@ -107,7 +109,7 @@ void PHOSTurnonSlot::scanClusters(const gsl::span& cells, const Trig if (clu.getEnergy() < 1.e-4) { continue; } - mod = clu.module(); + char mod = clu.module(); clu.getLocalPosition(x, z); // TODO: do we need separate 2x2 and 4x4 spectra? Switch? // short truId2x2 = Geometry::relPosToTruId(mod, x, z, 0); @@ -123,7 +125,7 @@ void PHOSTurnonSlot::scanClusters(const gsl::span& cells, const Trig // Fill final good and noisy maps mTurnOnHistos->fillFiredMap(mFiredTiles); mNoisyTiles ^= mFiredTiles; - mTurnOnHistos->fillNoisyMap(mFiredTiles); + mTurnOnHistos->fillNoisyMap(mNoisyTiles); } //============================================== diff --git a/Detectors/PHOS/reconstruction/include/PHOSReconstruction/CTFCoder.h b/Detectors/PHOS/reconstruction/include/PHOSReconstruction/CTFCoder.h index 96ee5093bacca..8c73114bc53bb 100644 --- a/Detectors/PHOS/reconstruction/include/PHOSReconstruction/CTFCoder.h +++ b/Detectors/PHOS/reconstruction/include/PHOSReconstruction/CTFCoder.h @@ -32,10 +32,10 @@ namespace o2 namespace phos { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::PHS) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::PHS, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/PHOS/reconstruction/src/CaloRawFitter.cxx b/Detectors/PHOS/reconstruction/src/CaloRawFitter.cxx index 37ba3ecae7159..aa929f8d6c6e5 100644 --- a/Detectors/PHOS/reconstruction/src/CaloRawFitter.cxx +++ b/Detectors/PHOS/reconstruction/src/CaloRawFitter.cxx @@ -13,6 +13,7 @@ /// \author Dmitri Peresunko #include +#include #include "PHOSReconstruction/CaloRawFitter.h" #include "PHOSBase/PHOSSimParams.h" diff --git a/Detectors/PHOS/reconstruction/src/CaloRawFitterGS.cxx b/Detectors/PHOS/reconstruction/src/CaloRawFitterGS.cxx index 08c1f7fb14bc1..3ea506cf203aa 100644 --- a/Detectors/PHOS/reconstruction/src/CaloRawFitterGS.cxx +++ b/Detectors/PHOS/reconstruction/src/CaloRawFitterGS.cxx @@ -13,6 +13,8 @@ /// \author Dmitri Peresunko #include +#include +#include #include "PHOSReconstruction/CaloRawFitterGS.h" #include "PHOSBase/PHOSSimParams.h" diff --git a/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyDecoderSpec.h b/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyDecoderSpec.h index 1890864af77ea..a6045cf36f7b2 100644 --- a/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyDecoderSpec.h +++ b/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace phos class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace phos } // namespace o2 diff --git a/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyEncoderSpec.h b/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyEncoderSpec.h index 4ac8240f4c234..c88bddedc7e20 100644 --- a/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyEncoderSpec.h +++ b/Detectors/PHOS/workflow/include/PHOSWorkflow/EntropyEncoderSpec.h @@ -28,7 +28,7 @@ namespace phos class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -42,7 +42,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace phos } // namespace o2 diff --git a/Detectors/PHOS/workflow/src/EntropyDecoderSpec.cxx b/Detectors/PHOS/workflow/src/EntropyDecoderSpec.cxx index a3d15862a2057..20b161b2d2325 100644 --- a/Detectors/PHOS/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/PHOS/workflow/src/EntropyDecoderSpec.cxx @@ -24,8 +24,7 @@ namespace o2 { namespace phos { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -74,7 +73,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"triggers"}, "PHS", "CELLTRIGREC", 0, Lifetime::Timeframe}, @@ -83,17 +82,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_PHS", "PHS", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_PHS", "PHS", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("PHS/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_PHS", "PHS", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("PHS/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "phos-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace phos } // namespace o2 diff --git a/Detectors/PHOS/workflow/src/EntropyEncoderSpec.cxx b/Detectors/PHOS/workflow/src/EntropyEncoderSpec.cxx index a932a45f1bb53..66a0e04ed3895 100644 --- a/Detectors/PHOS/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/PHOS/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace phos { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -70,12 +69,15 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("triggers", "PHS", "CELLTRIGREC", 0, Lifetime::Timeframe); inputs.emplace_back("cells", "PHS", "CELLS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "PHS", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("PHS/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "PHS", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("PHS/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -84,13 +86,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"PHS", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "PHS", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace phos } // namespace o2 diff --git a/Detectors/PHOS/workflow/src/StandaloneAODProducerSpec.cxx b/Detectors/PHOS/workflow/src/StandaloneAODProducerSpec.cxx index 454be7a5fcb83..06baf889b662f 100644 --- a/Detectors/PHOS/workflow/src/StandaloneAODProducerSpec.cxx +++ b/Detectors/PHOS/workflow/src/StandaloneAODProducerSpec.cxx @@ -19,7 +19,6 @@ #include "Framework/InputRecordWalker.h" #include "Framework/Logger.h" #include "Framework/TableBuilder.h" -#include "Framework/TableTreeHelpers.h" #include "MathUtils/Utils.h" using namespace o2::framework; @@ -106,7 +105,7 @@ void StandaloneAODProducerSpec::run(ProcessingContext& pc) o2::math_utils::detail::truncateFloatFraction(c.getTime(), mCaloTime), c.getType(), // HG/LG 0); // hard coded for phos (-1 would be undefined, 0 phos) - } // end of cell loop + } // end of cell loop auto bcID = tr.getBCData().toLong(); bcCursor(0, diff --git a/Detectors/PHOS/workflow/src/entropy-encoder-workflow.cxx b/Detectors/PHOS/workflow/src/entropy-encoder-workflow.cxx index c7266925060c2..1fc827a5a7525 100644 --- a/Detectors/PHOS/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/PHOS/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::phos::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::phos::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/Passive/CMakeLists.txt b/Detectors/Passive/CMakeLists.txt index 0976530bc6571..a24954ad10539 100644 --- a/Detectors/Passive/CMakeLists.txt +++ b/Detectors/Passive/CMakeLists.txt @@ -23,6 +23,7 @@ o2_add_library(DetectorsPassive src/Hall.cxx src/HallSimParam.cxx src/PassiveBase.cxx + src/ExternalModule.cxx PUBLIC_LINK_LIBRARIES O2::Field O2::DetectorsBase O2::SimConfig) o2_target_root_dictionary(DetectorsPassive @@ -39,6 +40,7 @@ o2_target_root_dictionary(DetectorsPassive include/DetectorsPassive/Hall.h include/DetectorsPassive/HallSimParam.h include/DetectorsPassive/PassiveBase.h + include/DetectorsPassive/ExternalModule.h LINKDEF src/PassiveLinkDef.h) # FIXME: if PutFrameInTop really depends on TRD, then the following can not work diff --git a/Detectors/Passive/include/DetectorsPassive/ExternalModule.h b/Detectors/Passive/include/DetectorsPassive/ExternalModule.h new file mode 100644 index 0000000000000..155870ae42a6d --- /dev/null +++ b/Detectors/Passive/include/DetectorsPassive/ExternalModule.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 ALICEO2_PASSIVE_EXTERNALMODULE_H +#define ALICEO2_PASSIVE_EXTERNALMODULE_H + +#include "DetectorsPassive/PassiveBase.h" // base class of passive modules +#include "Rtypes.h" // for Pipe::Class, ClassDef, Pipe::Streamer + +class TGeoVolume; +class TGeoTransformation; + +namespace o2 +{ +namespace passive +{ + +// options used to configure a generic plug and play external module +struct ExternalModuleOptions { + std::string root_macro_file; // the file where to lookup the ROOT geometry building macro + std::string top_volume; // the volume to be added + std::string anchor_volume; // the volume into which the detector will be hooked + TGeoMatrix const* placement = nullptr; // how to place the module inside anchor_volume +}; + +// a module (passive material) defined externally (ROOT macro / GDML / TGeo geometry) +class ExternalModule : public PassiveBase +{ + public: + ExternalModule(const char* name, const char* long_title, ExternalModuleOptions options); + ExternalModule() = default; // default constructor + + ~ExternalModule() override = default; + void ConstructGeometry() override; + + /// Clone this object (used in MT mode only) + FairModule* CloneModule() const override { return nullptr; } + + typedef std::function GeomBuilderFcn; // function hook for external geometry builder + + private: + // void createMaterials(); + ExternalModule(const ExternalModule& orig); + ExternalModule& operator=(const ExternalModule&); + + GeomBuilderFcn mGeomHook; + ExternalModuleOptions mOptions; + + bool initGeomBuilderHook(); // function to load/JIT Geometry builder hook + void remapMedia(TGeoVolume* vol); // performs a remapping of materials/media IDs after registration with VMC + + // ClassDefOverride(ExternalModule, 0); +}; +} // namespace passive +} // namespace o2 +#endif diff --git a/Detectors/Passive/include/DetectorsPassive/PipeRun4.h b/Detectors/Passive/include/DetectorsPassive/PipeRun4.h index 1943bb25a802f..5eadb7af1003c 100644 --- a/Detectors/Passive/include/DetectorsPassive/PipeRun4.h +++ b/Detectors/Passive/include/DetectorsPassive/PipeRun4.h @@ -46,7 +46,7 @@ class PipeRun4 : public PassiveBase TGeoPcon* makeMotherFromTemplate(const TGeoPcon* shape, int imin = -1, int imax = -1, float r0 = 0., int nz = -1); TGeoPcon* makeInsulationFromTemplate(TGeoPcon* shape); - TGeoVolume* makeBellow(const char* ext, int nc, float rMin, float rMax, float dU, float rPlie, + TGeoVolume* makeBellow(const char* ext, int nc, float rMin, float rMax, float rPlie, float dPlie); TGeoVolume* makeBellowCside(const char* ext, int nc, float rMin, float rMax, float rPlie, float dPlie); diff --git a/Detectors/Passive/src/Cave.cxx b/Detectors/Passive/src/Cave.cxx index e2ea513095c72..208084a335ab5 100644 --- a/Detectors/Passive/src/Cave.cxx +++ b/Detectors/Passive/src/Cave.cxx @@ -85,23 +85,27 @@ void Cave::ConstructGeometry() shCaveTR1->DefineSection(0, -706. - 8.6, 0., 790.5); shCaveTR1->DefineSection(1, 707. + 7.6, 0., 790.5); TGeoTube* shCaveTR2 = new TGeoTube("shCaveTR2", 0., 150., 110.); + TGeoTube* shCaveTR3 = new TGeoTube("shCaveTR3", 0., 80., 75.); TGeoTranslation* transCaveTR2 = new TGeoTranslation("transTR2", 0, 30., -505. - 110.); + TGeoTranslation* transCaveTR3 = new TGeoTranslation("transTR3", 0, 30., 714.6 + 75.); transCaveTR2->RegisterYourself(); - TGeoCompositeShape* shCaveTR = new TGeoCompositeShape("shCaveTR", "shCaveTR1-shCaveTR2:transTR2"); + transCaveTR3->RegisterYourself(); + + TGeoCompositeShape* shCaveTR = new TGeoCompositeShape("shCaveTR", "shCaveTR1-shCaveTR2:transTR2+shCaveTR3:transTR3"); TGeoVolume* voBarrel = new TGeoVolume("barrel", shCaveTR, kMedAir); cavevol->AddNode(voBarrel, 1, new TGeoTranslation(0., -30., 0.)); if (mHasRB24) { // should be not true only for alice 3 // mother volume for RB24 side (FDD, Compensator) - const Float_t kRB24CL = 2. * 597.9; + const Float_t kRB24CL = 2. * 597.9 - 150.; auto shCaveRB24 = new TGeoPcon(0., 360., 6); - Float_t z0 = kRB24CL / 2 + 714.6; + Float_t z0 = kRB24CL / 2 + 714.6 + 150.; shCaveRB24->DefineSection(0, -kRB24CL / 2., 0., 105.); shCaveRB24->DefineSection(1, -z0 + 1705., 0., 105.); shCaveRB24->DefineSection(2, -z0 + 1705., 0., 14.5); - shCaveRB24->DefineSection(3, -z0 + 1880., 0., 14.5); - shCaveRB24->DefineSection(4, -z0 + 1880., 0., 40.0); - shCaveRB24->DefineSection(5, kRB24CL / 2, 0., 40.0); + shCaveRB24->DefineSection(3, -z0 + 1878, 0., 14.5); + shCaveRB24->DefineSection(4, -z0 + 1878., 0., 40.0); + shCaveRB24->DefineSection(5, kRB24CL / 2., 0., 40.0); TGeoVolume* caveRB24 = new TGeoVolume("caveRB24", shCaveRB24, kMedAir); caveRB24->SetVisibility(0); diff --git a/Detectors/Passive/src/Compensator.cxx b/Detectors/Passive/src/Compensator.cxx index 68e344495aaab..25b3e2a475340 100644 --- a/Detectors/Passive/src/Compensator.cxx +++ b/Detectors/Passive/src/Compensator.cxx @@ -110,7 +110,7 @@ void Compensator::ConstructGeometry() void Compensator::createCompensator() { auto top = gGeoManager->GetVolume("caveRB24"); - top->AddNode(createMagnetYoke(), 1, new TGeoTranslation(0., 0., 1075. - 1313.347)); + top->AddNode(createMagnetYoke(), 1, new TGeoTranslation(0., 0., 1000. - 1313.347)); } TGeoVolume* Compensator::createMagnetYoke() diff --git a/Detectors/Passive/src/ExternalModule.cxx b/Detectors/Passive/src/ExternalModule.cxx new file mode 100644 index 0000000000000..fc6bd6953b82d --- /dev/null +++ b/Detectors/Passive/src/ExternalModule.cxx @@ -0,0 +1,175 @@ +// 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. + +// Sandro Wenzel (CERN), 2026 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ClassImp(o2::passive::ExternalModule) + +namespace o2::passive +{ + +ExternalModule::ExternalModule(const char* name, const char* long_title, ExternalModuleOptions options) : PassiveBase(name, long_title), mOptions(options) +{ +} + +void ExternalModule::remapMedia(TGeoVolume* top_volume) +{ + std::unordered_map medium_ptr_mapping; + std::unordered_set volumes_already_treated; + int counter = 1; + + auto modulename = GetName(); + + // The transformer function + auto transform_media = [&](TGeoVolume* vol_) { + if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) { + // this volume was already transformed + return; + } + volumes_already_treated.insert(vol_); + + if (dynamic_cast(vol_)) { + // do nothing for assemblies (they don't have a medium) + return; + } + + auto medium = vol_->GetMedium(); + if (!medium) { + return; + } + + auto iter = medium_ptr_mapping.find(medium); + if (iter != medium_ptr_mapping.end()) { + // This medium has already been transformed, so + // we just update the volume + vol_->SetMedium(iter->second); + return; + } else { + std::cout << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName() << "\n"; + + // we found a medium, not yet treated + auto curr_mat = medium->GetMaterial(); + auto& matmgr = o2::base::MaterialManager::Instance(); + + matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen()); + // TGeo medium params are stored in a flat array with the following convention + // fParams[0] = isvol; + // fParams[1] = ifield; + // fParams[2] = fieldm; + // fParams[3] = tmaxfd; + // fParams[4] = stemax; + // fParams[5] = deemax; + // fParams[6] = epsil; + // fParams[7] = stmin; + const auto isvol = medium->GetParam(0); + const auto isxfld = medium->GetParam(1); + const auto sxmgmx = medium->GetParam(2); + const auto tmaxfd = medium->GetParam(3); + const auto stemax = medium->GetParam(4); + const auto deemax = medium->GetParam(5); + const auto epsil = medium->GetParam(6); + const auto stmin = medium->GetParam(7); + + matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin); + + // there will be new Material and Medium objects; fetch them + auto new_med = matmgr.getTGeoMedium(modulename, counter); + + // insert into cache + medium_ptr_mapping[medium] = new_med; + vol_->SetMedium(new_med); + counter++; + } + }; // end transformer lambda + + // a generic volume walker + std::function visit_volume; + visit_volume = [&](TGeoVolume* vol) -> void { + if (!vol) { + return; + } + + // call the transformer + transform_media(vol); + + // Recurse into daughters + const int nd = vol->GetNdaughters(); + for (int i = 0; i < nd; ++i) { + TGeoNode* node = vol->GetNode(i); + if (!node) { + continue; + } + TGeoVolume* child = node->GetVolume(); + if (!child) { + continue; + } + + visit_volume(child); + } + }; + + visit_volume(top_volume); +} + +void ExternalModule::ConstructGeometry() +{ + // JIT the geom builder hook + if (!initGeomBuilderHook()) { + LOG(error) << " Could not load geometry builder hook"; + return; + } + + // otherwise execute it and obtain pointer to top most module volume + auto module_top = mGeomHook(); + if (!module_top) { + LOG(error) << "No module found\n"; + return; + } + + remapMedia(const_cast(module_top)); + + // place it into the provided anchor volume (needs to exist) + auto anchor = gGeoManager->FindVolumeFast(mOptions.anchor_volume.c_str()); + if (!anchor) { + LOG(error) << "Anchor volume " << mOptions.anchor_volume << " not found. Aborting"; + return; + } + anchor->AddNode(const_cast(module_top), 1, const_cast(mOptions.placement)); +} + +bool ExternalModule::initGeomBuilderHook() +{ + if (mOptions.root_macro_file.size() > 0) { + LOG(info) << "Initializing the hook for geometry module building"; + auto expandedHookFileName = o2::utils::expandShellVarsInFileName(mOptions.root_macro_file); + if (std::filesystem::exists(expandedHookFileName)) { + // if this file exists we will compile the hook on the fly (the last one is an identifier --> maybe make it dependent on this class) + mGeomHook = o2::conf::GetFromMacro(mOptions.root_macro_file, "get_builder_hook_unchecked()", "function", "o2_passive_extmodule_builder"); + LOG(info) << "Hook initialized from file " << expandedHookFileName; + return true; + } + } + return false; +} + +} // namespace o2::passive \ No newline at end of file diff --git a/Detectors/Passive/src/Pipe.cxx b/Detectors/Passive/src/Pipe.cxx index 56f6429bc73c8..56ccfc45f0b89 100644 --- a/Detectors/Passive/src/Pipe.cxx +++ b/Detectors/Passive/src/Pipe.cxx @@ -786,13 +786,13 @@ void Pipe::ConstructGeometry() // Copper Tube RB24/1 const Float_t kRB24CuTubeL = 381.5; - const Float_t kRB24cCuTubeL = 155.775; + const Float_t kRB24cCuTubeL = 155.775 - 150.; const Float_t kRB24bCuTubeL = kRB24CuTubeL - kRB24cCuTubeL; const Float_t kRB24CuTubeRi = 8.0 / 2.; const Float_t kRB24CuTubeRo = 8.4 / 2.; const Float_t kRB24CuTubeFRo = 7.6; const Float_t kRB24CuTubeFL = 1.86; - const Float_t kRB24CL = 2. * 597.9; + const Float_t kRB24CL = 2. * 597.9 - 150.; // // introduce cut at end of barrel 714.6m // @@ -812,7 +812,7 @@ void Pipe::ConstructGeometry() voRB24cCuTubeM->AddNode(voRB24cCuTube, 1, gGeoIdentity); // Air outside tube with higher transport cuts - TGeoVolume* voRB24CuTubeA = new TGeoVolume("voRB24CuTubeA", new TGeoTube(80., 81., kRB24bCuTubeL / 2.), kMedAirHigh); + TGeoVolume* voRB24CuTubeA = new TGeoVolume("voRB24CuTubeA", new TGeoTube(79., 80., kRB24bCuTubeL / 2.), kMedAirHigh); voRB24CuTubeA->SetVisibility(0); // Simplified DN 100 Flange diff --git a/Detectors/Passive/src/PipeRun4.cxx b/Detectors/Passive/src/PipeRun4.cxx index 7a2ff6dcfe90b..5aa0b63a6ac78 100644 --- a/Detectors/Passive/src/PipeRun4.cxx +++ b/Detectors/Passive/src/PipeRun4.cxx @@ -215,8 +215,7 @@ void PipeRun4::ConstructGeometry() voberylliumTube->SetLineColor(kRed); TGeoTube* berylliumTubeVacuum = - new TGeoTube("IP_PIPEVACUUMsh", 0., kBeryliumSectionOuterRadius - kBeryliumSectionThickness, - (kBeryliumSectionZmax - kBeryliumSectionZmin) / 2); + new TGeoTube("IP_PIPEVACUUMsh", 0., kBeryliumSectionOuterRadius, (kBeryliumSectionZmax - kBeryliumSectionZmin) / 2); TGeoVolume* voberylliumTubeVacuum = new TGeoVolume("IP_PIPEMOTHER", berylliumTubeVacuum, kMedVac); voberylliumTubeVacuum->AddNode(voberylliumTube, 1, gGeoIdentity); voberylliumTubeVacuum->SetVisibility(0); @@ -643,7 +642,7 @@ void PipeRun4::ConstructGeometry() // Drawings from C. Gargiulo : // \\cern.ch\dfs\Workspaces\c\cgargiul\EXPERIMENT\ALICE\ALICE_MECHANICS\ALICE_DATA_PACKAGE\IN\DETECTORS\ITS_UPGRADE\1-DESIGN\0-IF_Control_Drawing\20140207_ICD_ITS_MFT_BP ///////////////////////////////////////////////////////////////////// - + TGeoVolumeAssembly* beamPipeAsideSection = new TGeoVolumeAssembly("BeamPipeAsideSection"); float kConicalBerilliumMinThickness = 0.08; float kConicalBerilliumMaxThickness = 0.1; float kFlangeZ = 483.75; @@ -657,33 +656,34 @@ void PipeRun4::ConstructGeometry() float kConicalBePipeEndOuterRadius = 3.0; TGeoPcon* tube0 = new TGeoPcon(0., 360., 5); - tube0->DefineSection(0, kFlangeZ - kFlangeWidth / 2, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); - tube0->DefineSection(1, kConicalBerylliumEnd, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); + tube0->DefineSection(0, kFlangeZ - kFlangeWidth / 2, kConicalBePipeEndOuterRadius - kConicalBerilliumMinThickness, kConicalBePipeEndOuterRadius); + tube0->DefineSection(1, kConicalBerylliumEnd, kConicalBePipeEndOuterRadius - kConicalBerilliumMinThickness, kConicalBePipeEndOuterRadius); tube0->DefineSection(2, kSupport1 + kSupportWidth, kPipeRadiusAtSupport1 - kConicalBerilliumMinThickness, kPipeRadiusAtSupport1); tube0->DefineSection(3, kSupport1, kPipeRadiusAtSupport1 - kConicalBerilliumMinThickness, kPipeRadiusAtSupport1); tube0->DefineSection(4, kBeryliumSectionZmax, kBeryliumSectionOuterRadius - kConicalBerilliumMinThickness, kBeryliumSectionOuterRadius); // need a transition to kConicalBerilliumMaxThickness - TGeoPcon* tube0vide = new TGeoPcon(0., 360., 5); - tube0vide->DefineSection(0, kFlangeZ - kFlangeWidth / 2, 0., kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness - 0.01); - tube0vide->DefineSection(1, kConicalBerylliumEnd, 0., kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness - 0.01); - tube0vide->DefineSection(2, kSupport1 + kSupportWidth, 0, kPipeRadiusAtSupport1 - kConicalBerilliumMinThickness - 0.01); - tube0vide->DefineSection(3, kSupport1, 0, kPipeRadiusAtSupport1 - kConicalBerilliumMinThickness - 0.01); - tube0vide->DefineSection(4, kBeryliumSectionZmax, 0., kBeryliumSectionOuterRadius - kConicalBerilliumMinThickness - 0.01); + TGeoPcon* tube0Mo = new TGeoPcon(0., 360., 5); + tube0Mo->DefineSection(0, kFlangeZ - kFlangeWidth / 2, 0., kConicalBePipeEndOuterRadius); + tube0Mo->DefineSection(1, kConicalBerylliumEnd, 0., kConicalBePipeEndOuterRadius); + tube0Mo->DefineSection(2, kSupport1 + kSupportWidth, 0, kPipeRadiusAtSupport1); + tube0Mo->DefineSection(3, kSupport1, 0, kPipeRadiusAtSupport1); + tube0Mo->DefineSection(4, kBeryliumSectionZmax, 0., kBeryliumSectionOuterRadius); TGeoVolume* votube0 = new TGeoVolume("votube0", tube0, kMedBe); votube0->SetLineColor(kRed); - TGeoVolume* votube0vide = new TGeoVolume("votube0vide", tube0vide, kMedVac); - votube0vide->SetLineColor(kGreen); + TGeoVolume* votube0Mo = new TGeoVolume("votube0Mo", tube0Mo, kMedVac); + votube0Mo->AddNode(votube0, 1, gGeoIdentity); + votube0Mo->SetLineColor(kGreen); - barrel->AddNode(votube0, 1, new TGeoTranslation(0., 30., 0.)); - barrel->AddNode(votube0vide, 1, new TGeoTranslation(0., 30., 0.)); + beamPipeAsideSection->AddNode(votube0Mo, 1, gGeoIdentity); - TGeoVolume* beampipeSupportA1 = makeSupportBar("A1", kPipeRadiusAtSupport1 + 0.01, kPipeRadiusAtSupport1 + 0.38, 20.67, 14.25); - barrel->AddNode(beampipeSupportA1, 1, new TGeoTranslation(0., 30, kSupport1 + kSupportWidth / 2.)); + // already defined in IT3 + // TGeoVolume* beampipeSupportA1 = makeSupportBar("A1", kPipeRadiusAtSupport1 + 0.01, kPipeRadiusAtSupport1 + 0.38, 20.67, 14.25); + // beamPipeAsideSection->AddNode(beampipeSupportA1, 1, new TGeoTranslation(0., 0., kSupport1 + kSupportWidth / 2.)); // Length is approximate TGeoVolume* beampipeSupportA2 = makeSupportBar("A2", kConicalBePipeEndOuterRadius, kConicalBePipeEndOuterRadius + 0.38, 44, 37.5); - barrel->AddNode(beampipeSupportA2, 1, new TGeoTranslation(0., 30, kConicalBerylliumEnd + kSupportWidth / 2.)); + beamPipeAsideSection->AddNode(beampipeSupportA2, 1, new TGeoTranslation(0., 0., kConicalBerylliumEnd + kSupportWidth / 2.)); TGeoPcon* Bolt1 = new TGeoPcon(0., 360, 8); Bolt1->DefineSection(0, 0, 0, 0.5); @@ -735,7 +735,7 @@ void PipeRun4::ConstructGeometry() Bolts->AddNode(volBolt1, 7, t7); Bolts->AddNode(volBolt1, 8, t8); - barrel->AddNode(Bolts, 1, new TGeoTranslation(0., 30., 0.)); + beamPipeAsideSection->AddNode(Bolts, 1, gGeoIdentity); TGeoTranslation* Tflange = new TGeoTranslation(0, 0, kFlangeZ); Tflange->SetName("Tflange"); @@ -754,53 +754,40 @@ void PipeRun4::ConstructGeometry() TGeoVolume* volflange = new TGeoVolume("voFlangeHoles", FlangeWithHoles, kMedAlBe); volflange->SetLineWidth(2); volflange->SetLineColor(kGray); - - barrel->AddNode(volflange, 1, new TGeoTranslation(0., 30., 0.)); - - TGeoPcon* pipeSamell = new TGeoPcon(0., 360., 2); - pipeSamell->DefineSection(0, kFlangeZ + kFlangeWidth / 2, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); - pipeSamell->DefineSection(1, kFlangeZ + 5.13 + 0.435 + 0.4 + 0.08, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); - pipeSamell->SetName("pipeSamell"); - - TGeoVolume* VolpipeSmall = new TGeoVolume("voPipeSmallVac", pipeSamell, kMedAlu2219); - VolpipeSmall->SetLineWidth(2); - barrel->AddNode(VolpipeSmall, 1, new TGeoTranslation(0., 30., 0.)); - - TGeoPcon* pipeSmallVac = new TGeoPcon(0., 360., 2); - pipeSmallVac->DefineSection(0, kFlangeZ + kFlangeWidth / 2, 0, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness - 0.01); - pipeSmallVac->DefineSection(1, kFlangeZ + 5.13 + 0.435 + 0.4 + 0.08, 0, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness - 0.01); - TGeoVolume* vopipeSmallVac = new TGeoVolume("voPipeSmallVac", pipeSmallVac, kMedVac); - vopipeSmallVac->SetLineColor(kGreen); - - barrel->AddNode(vopipeSmallVac, 1, new TGeoTranslation(0., 30., 0.)); + beamPipeAsideSection->AddNode(volflange, 1, gGeoIdentity); // -- Bellows on A side - // float plieradius = (3.72 + (2. * 7 - 2.) * 0.03) / (4. * 7); // radius of bellows "plis" float plieradiusA = 0.2; // radius of bellow plies - // ------------------ First Bellow -------------------- // Inner: 3.0 cm, outer 3.97 cm length 8.47 cm with 10 wiggles - // check meaning of dU ; it is probably the total length, see also below - TGeoVolume* vobellows1A = makeBellow("bellows1A", 10, 3.0, 3.97, 8.47, plieradiusA, 0.03); - // Z position is rough for now. - barrel->AddNode(vobellows1A, 1, new TGeoTranslation(0., 30., kFlangeZ + 10)); + TGeoVolume* vobellows1A = makeBellow("bellows1A", 10, 3.0, 3.97, plieradiusA, 0.03); + Float_t dU = (static_cast(vobellows1A->GetShape()))->GetDZ(); + beamPipeAsideSection->AddNode(vobellows1A, 1, new TGeoTranslation(0., 0., kFlangeZ + 2. * dU)); // Comments: removing 1/2 plie (see makeBellow): 0.31= 2*0.17-0.03 and 0.08: free space + Float_t pipeSmallDz = (dU - kFlangeWidth / 2.) / 2.; + TGeoTube* pipeSmall = new TGeoTube("pipeSmall", kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius, pipeSmallDz); + TGeoVolume* vopipeSmall = new TGeoVolume("voPipeSmall", pipeSmall, kMedAlu2219); + vopipeSmall->SetLineWidth(2); - // ------------------ Outer pipe after flange -------------------- - TGeoPcon* pipeOut = new TGeoPcon(0., 360., 2); - pipeOut->DefineSection(0, kFlangeZ + 13.6 - 0.08, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); - pipeOut->DefineSection(1, 714.6, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius); + TGeoTube* pipeSmallMo = new TGeoTube(0., kConicalBePipeEndOuterRadius, pipeSmallDz); + TGeoVolume* vopipeSmallMo = new TGeoVolume("voPipeSmallMo", pipeSmallMo, kMedVac); + vopipeSmallMo->SetLineColor(kGreen); + vopipeSmallMo->AddNode(vopipeSmall, 1, gGeoIdentity); - TGeoVolume* OuterPIPE = new TGeoVolume("pipeOut", pipeOut, kMedAlu2219); - barrel->AddNode(OuterPIPE, 1, new TGeoTranslation(0., 30., 0.)); + beamPipeAsideSection->AddNode(vopipeSmallMo, 1, new TGeoTranslation(0., 0., kFlangeZ + kFlangeWidth / 2. + pipeSmallDz)); - // The end of the barrel volume is at 714.6 cm, after that we start with RB24 volume - TGeoPcon* pipeOutVac = new TGeoPcon(0., 360., 2); - pipeOutVac->DefineSection(0, kFlangeZ + 13.6 - 0.08, 0, kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness); - pipeOutVac->DefineSection(1, 714.6, 0., kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness); + // ------------------ Outer pipe after flange -------------------- + // The end of the barrel volume is at 864.6 cm, after that we start with RB24 volume + Float_t pipeEndZ = 864.6; + Float_t pipeOutDz = (pipeEndZ - (kFlangeZ + 3. * dU)) / 2.; + TGeoTube* pipeOut = new TGeoTube(kConicalBePipeEndOuterRadius - kConicalBerilliumMaxThickness, kConicalBePipeEndOuterRadius, pipeOutDz); + TGeoVolume* OuterPIPE = new TGeoVolume("pipeOut", pipeOut, kMedAlu2219); - TGeoVolume* OuterPIPEVac = new TGeoVolume("pipeOutVac", pipeOutVac, kMedAlu2219); - barrel->AddNode(OuterPIPEVac, 1, new TGeoTranslation(0., 30., 0.)); + TGeoTube* pipeOutMo = new TGeoTube(0., kConicalBePipeEndOuterRadius, pipeOutDz); + TGeoVolume* OuterPIPEMo = new TGeoVolume("pipeOutMo", pipeOutMo, kMedVac); + OuterPIPEMo->AddNode(OuterPIPE, 1, gGeoIdentity); + beamPipeAsideSection->AddNode(OuterPIPEMo, 1, new TGeoTranslation(0., 0., pipeEndZ - pipeOutDz)); + barrel->AddNode(beamPipeAsideSection, 1, new TGeoTranslation(0., 30., 0.)); //------------------------------------------------- @@ -823,19 +810,19 @@ void PipeRun4::ConstructGeometry() // Copper Tube RB24/1 const float kRB24CuTubeL = 381.5; - const float kRB24cCuTubeL = 155.775 + (28.375 - 18.135); + const float kRB24cCuTubeL = 155.775 - 150.; const float kRB24bCuTubeL = kRB24CuTubeL - kRB24cCuTubeL; const float kRB24CuTubeRi = 5.8 / 2.; const float kRB24CuTubeRo = 6.0 / 2.; const float kRB24CuTubeFRo = 7.6; const float kRB24CuTubeFL = 1.86; - const float kRB24CL = 2. * 597.9; + const float kRB24CL = 2. * 597.9 - 150.; // // introduce cut at end of barrel 714.6m // // outside barrel - TGeoVolume* voRB24cCuTubeM = new TGeoVolume("voRB24cCuTubeM", new TGeoTube(0., kRB24CuTubeRi, kRB24cCuTubeL / 2.), kMedVacNFHC); + TGeoVolume* voRB24cCuTubeM = new TGeoVolume("voRB24cCuTubeM", new TGeoTube(0., kRB24CuTubeRo, kRB24cCuTubeL / 2.), kMedVacNFHC); TGeoVolume* voRB24cCuTube = new TGeoVolume("voRB24cCuTube", new TGeoTube(kRB24CuTubeRi, kRB24CuTubeRo, kRB24cCuTubeL / 2.), kMedAlu2219); voRB24cCuTubeM->AddNode(voRB24cCuTube, 1, gGeoIdentity); @@ -877,7 +864,7 @@ void PipeRun4::ConstructGeometry() const float kRB24B1PlieThickness = 0.015; // Plie thickness const float kRB24B1PlieRadius = - (kRB24B1BellowUndL + (2. * kRB24B1NumberOfPlies - 2.) * kRB24B1PlieThickness) / (4. * kRB24B1NumberOfPlies); + (kRB24B1BellowUndL + 2. * kRB24B1NumberOfPlies * kRB24B1PlieThickness) / (4. * kRB24B1NumberOfPlies + 2.); const float kRB24B1ProtTubeThickness = 0.02; // Thickness of the protection tube const float kRB24B1ProtTubeLength = 4.2; // Length of the protection tube @@ -893,7 +880,7 @@ void PipeRun4::ConstructGeometry() // // Bellow Section TGeoVolume* voRB24B1Bellow = makeBellow("RB24B1", kRB24B1NumberOfPlies, kRB24B1BellowRi, kRB24B1BellowRo, - kRB24B1BellowUndL, kRB24B1PlieRadius, kRB24B1PlieThickness); + kRB24B1PlieRadius, kRB24B1PlieThickness); voRB24B1Bellow->SetVisibility(0); float newRB24B1BellowUndL = 2 * (static_cast(voRB24B1Bellow->GetShape()))->GetDz(); @@ -2841,13 +2828,11 @@ TGeoPcon* PipeRun4::makeInsulationFromTemplate(TGeoPcon* shape) return insu; } -TGeoVolume* PipeRun4::makeBellow(const char* ext, int nc, float rMin, float rMax, float dU, float rPlie, - float dPlie) +TGeoVolume* PipeRun4::makeBellow(const char* ext, int nc, float rMin, float rMax, float rPlie, float dPlie) { // nc Number of convolution // rMin Inner radius of the bellow // rMax Outer radius of the bellow - // dU Undulation length // rPlie Plie radius // dPlie Plie thickness auto& matmgr = o2::base::MaterialManager::Instance(); @@ -2897,10 +2882,10 @@ TGeoVolume* PipeRun4::makeBellow(const char* ext, int nc, float rMin, float rMax asWiggle->AddNode(voWiggleL, 1, new TGeoTranslation(0., 0., z0)); asWiggle->GetShape()->ComputeBBox(); // enforce recomputing of BBox // - float zBellowTot = nc * (static_cast(asWiggle->GetShape()))->GetDZ(); - TGeoVolume* voBellow = new TGeoVolume(fmt::format("{:s}BellowUS", ext).c_str(), new TGeoTube(rMin, rMax, zBellowTot), kMedVac); + float zBellowTot = nc * (2. * (static_cast(asWiggle->GetShape()))->GetDZ() - dPlie) + 2. * rPlie; + TGeoVolume* voBellow = new TGeoVolume(fmt::format("{:s}BellowUS", ext).c_str(), new TGeoTube(rMin, rMax, zBellowTot / 2.), kMedVac); // Positioning of the volumes - z0 = -dU / 2. + rPlie; + z0 = -zBellowTot / 2. + rPlie; voBellow->AddNode(voWiggleL, 2, new TGeoTranslation(0., 0., z0)); z0 += rPlie; float zsh = 4. * rPlie - 2. * dPlie; @@ -2908,6 +2893,7 @@ TGeoVolume* PipeRun4::makeBellow(const char* ext, int nc, float rMin, float rMax float zpos = z0 + iw * zsh; voBellow->AddNode(asWiggle, iw + 1, new TGeoTranslation(0., 0., zpos - dPlie)); } + return voBellow; } 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/GPU/GPUTracking/SectorTracker/GPUTPCDefinitions.h b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h similarity index 66% rename from GPU/GPUTracking/SectorTracker/GPUTPCDefinitions.h rename to Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h index 7d9d607b9b88d..a39cfb026ed52 100644 --- a/GPU/GPUTracking/SectorTracker/GPUTPCDefinitions.h +++ b/Detectors/Raw/TFReaderDD/src/RawTFDumpSpec.h @@ -9,17 +9,15 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file GPUTPCDefinitions.h -/// \author Sergey Gorbunov, David Rohr +#ifndef O2_RAW_TF_DUMP_SPEC_ +#define O2_RAW_TF_DUMP_SPEC_ -#ifndef GPUTPCDEFINITIONS_H -#define GPUTPCDEFINITIONS_H +#include "DetectorsCommonDataFormats/DetID.h" +#include "Framework/DeviceSpec.h" -#include "AliHLTDataTypes.h" - -namespace GPUTPCDefinitions +namespace o2::rawdd { -extern const AliHLTComponentDataType fgkTrackletsDataType; +o2::framework::DataProcessorSpec getRawTFDumpSpec(const std::string& inpconfig, const std::string& trigger); } -#endif // GPUTPCDEFINITIONS_H +#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/CalibTOFapi.h b/Detectors/TOF/base/include/TOFBase/CalibTOFapi.h index c3d39d3e978e1..6fb87e72abf62 100644 --- a/Detectors/TOF/base/include/TOFBase/CalibTOFapi.h +++ b/Detectors/TOF/base/include/TOFBase/CalibTOFapi.h @@ -23,6 +23,9 @@ #include "TOFBase/Geo.h" #include "DataFormatsTOF/Diagnostic.h" #include "DataFormatsTOF/TOFFEElightInfo.h" +#include "DataFormatsTOF/CompressedDataFormat.h" + +class TH2F; namespace o2 { @@ -38,10 +41,12 @@ class CalibTOFapi using CcdbApi = o2::ccdb::CcdbApi; public: + static o2::tof::Diagnostic doDRMerrCalibFromQCHisto(const TH2F* histo, const char* file_output_name); + void resetDia(); CalibTOFapi() = default; CalibTOFapi(const std::string url); - CalibTOFapi(long timestamp, o2::dataformats::CalibLHCphaseTOF* phase, o2::dataformats::CalibTimeSlewingParamTOF* slew, Diagnostic* dia = nullptr) : mTimeStamp(timestamp), mLHCphase(phase), mSlewParam(slew), mDiaFreq(dia) {} + CalibTOFapi(long timestamp, o2::dataformats::CalibLHCphaseTOF* phase, o2::dataformats::CalibTimeSlewingParamTOF* slew, Diagnostic* dia = nullptr, Diagnostic* diaDRM = nullptr) : mTimeStamp(timestamp), mLHCphase(phase), mSlewParam(slew), mDiaFreq(dia), mDiaDRMFreq(diaDRM) {} ~CalibTOFapi() { if (mLHCphase) { @@ -53,6 +58,9 @@ class CalibTOFapi if (mDiaFreq) { // delete mDiaFreq; } + if (mDiaDRMFreq) { + // delete mDiaDRMFreq; + } } void setTimeStamp(long t) @@ -69,6 +77,8 @@ class CalibTOFapi void readTimeSlewingParamFromFile(const char* filename); void readDiagnosticFrequencies(); void loadDiagnosticFrequencies(); + void readDiagnosticDRMFrequencies(); + void loadDiagnosticDRMFrequencies(); void readActiveMap(); void loadActiveMap(TOFFEElightInfo* fee); void writeLHCphase(LhcPhase* phase, std::map metadataLHCphase, uint64_t minTimeSTamp, uint64_t maxTimeStamp); @@ -89,6 +99,8 @@ class CalibTOFapi void setLhcPhase(LhcPhase* obj) { mLHCphase = obj; } Diagnostic* getDiagnostic() { return mDiaFreq; } void setDiagnostic(Diagnostic* obj) { mDiaFreq = obj; } + Diagnostic* getDiagnosticDRM() { return mDiaDRMFreq; } + void setDiagnosticDRM(Diagnostic* obj) { mDiaDRMFreq = obj; } int getNoisyThreshold() const { return mNoisyThreshold; } void setNoisyThreshold(int val) { mNoisyThreshold = val; } @@ -102,12 +114,39 @@ class CalibTOFapi void processError(int crate, int trm, int mask); bool isChannelError(int channel) const; bool checkTRMPolicy(int mask) const; + void resetDRMErrors(); + void processErrorDRM(int crate, int codeErr); + bool isChannelDRMError(int channel) const; + bool checkDRMPolicy(int mask) const; + + void setDRMCriticalErrorMask(uint32_t val) { mDRMCriticalErrorMask = val; } + uint32_t getDRMCriticalErrorMask() const { return mDRMCriticalErrorMask; } + float getDRMprobError(int crate, int type) const { return mErrorInDRM[crate][type]; } + + // DRM error codes inherited by EDRMDiagnostic_t in CompressedDataFormat.h (shifted by 4 bits) + static const int DRM_ERRINDEX_SHIFT = 4; + enum DRMcodes { + DRM_HEADER_MISSING = o2::tof::diagnostic::DRM_HEADER_MISSING >> DRM_ERRINDEX_SHIFT, + DRM_TRAILER_MISSING = o2::tof::diagnostic::DRM_TRAILER_MISSING >> DRM_ERRINDEX_SHIFT, + DRM_FEEID_MISMATCH = o2::tof::diagnostic::DRM_FEEID_MISMATCH >> DRM_ERRINDEX_SHIFT, + DRM_ORBIT_MISMATCH = o2::tof::diagnostic::DRM_ORBIT_MISMATCH >> DRM_ERRINDEX_SHIFT, + DRM_CRC_MISMATCH = o2::tof::diagnostic::DRM_CRC_MISMATCH >> DRM_ERRINDEX_SHIFT, + DRM_ENAPARTMASK_DIFFER = o2::tof::diagnostic::DRM_ENAPARTMASK_DIFFER >> DRM_ERRINDEX_SHIFT, + DRM_CLOCKSTATUS_WRONG = o2::tof::diagnostic::DRM_CLOCKSTATUS_WRONG >> DRM_ERRINDEX_SHIFT, + DRM_FAULTSLOTMASK_NOTZERO = o2::tof::diagnostic::DRM_FAULTSLOTMASK_NOTZERO >> DRM_ERRINDEX_SHIFT, + DRM_READOUTTIMEOUT_NOTZERO = o2::tof::diagnostic::DRM_READOUTTIMEOUT_NOTZERO >> DRM_ERRINDEX_SHIFT, + DRM_EVENTWORDS_MISMATCH = o2::tof::diagnostic::DRM_EVENTWORDS_MISMATCH >> DRM_ERRINDEX_SHIFT, + DRM_DIAGNOSTIC_SPARE1 = o2::tof::diagnostic::DRM_DIAGNOSTIC_SPARE1 >> DRM_ERRINDEX_SHIFT, + DRM_DECODE_ERROR = o2::tof::diagnostic::DRM_DECODE_ERROR >> DRM_ERRINDEX_SHIFT, + N_DRM_ERRORS = 12 + }; private: - long mTimeStamp; ///< timeStamp for queries - LhcPhase* mLHCphase = nullptr; ///< object for LHC phase - SlewParam* mSlewParam = nullptr; ///< object for timeslewing (containing info also for offset and problematic) - Diagnostic* mDiaFreq = nullptr; ///< object for Diagnostic Frequency + long mTimeStamp; ///< timeStamp for queries + LhcPhase* mLHCphase = nullptr; ///< object for LHC phase + SlewParam* mSlewParam = nullptr; ///< object for timeslewing (containing info also for offset and problematic) + Diagnostic* mDiaFreq = nullptr; ///< object for Diagnostic Frequency + Diagnostic* mDiaDRMFreq = nullptr; ///< object for Diagnostic Frequency // info from diagnostic int mNoisyThreshold = 1; ///< threshold to be noisy @@ -116,13 +155,17 @@ class CalibTOFapi std::vector> mNoisy; ///< probTRMerror std::vector> mTRMerrorProb; ///< probTRMerror std::vector mTRMmask; ///< mask error for TRM + float mErrorInDRM[Geo::kNCrate][N_DRM_ERRORS] = {}; ///< probability of DRM error + uint32_t mDRMCriticalErrorMask = 0; ///< bit mask for critical DRM errors (e.g. Orbit Mismatch -> 1 << 7, see DataFormats/Detectors/TOF/include/DataFormatsTOF/CompressedDataFormat.h) - bool mIsErrorCh[Geo::NCHANNELS] = {}; ///< channels in error (TRM) - std::vector mFillErrChannel; ///< last error channels filled - bool mIsOffCh[Geo::NCHANNELS] = {}; ///< channels in error (TRM) - bool mIsNoisy[Geo::NCHANNELS] = {}; ///< noisy channels + bool mIsErrorCh[Geo::NCHANNELS] = {}; ///< channels in error (TRM) + std::vector mFillErrChannel; ///< last error channels filled + bool mIsOffCh[Geo::NCHANNELS] = {}; ///< channels in error (TRM) + bool mIsNoisy[Geo::NCHANNELS] = {}; ///< noisy channels + bool mIsErrorDRMCh[Geo::NCHANNELS] = {}; ///< channels in error (DRM) + std::vector mFillErrDRMChannel; ///< last error channels filled - ClassDefNV(CalibTOFapi, 1); + ClassDefNV(CalibTOFapi, 2); }; } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/base/include/TOFBase/Digit.h b/Detectors/TOF/base/include/TOFBase/Digit.h index eef03ef84b97c..afa5662044a3e 100644 --- a/Detectors/TOF/base/include/TOFBase/Digit.h +++ b/Detectors/TOF/base/include/TOFBase/Digit.h @@ -101,13 +101,13 @@ class Digit private: friend class boost::serialization::access; - Int_t mChannel; ///< TOF channel index - uint16_t mTDC; ///< TDC bin number - uint16_t mTOT; ///< TOT bin number - InteractionRecord mIR{0, 0}; ///< InteractionRecord (orbit and bc) when digit occurs - Int_t mLabel; ///< Index of the corresponding entry in the MC label array - Double_t mCalibratedTime; //!< time of the digits after calibration (not persistent; it will be filled during clusterization) - Int_t mElectronIndex; //!/< index in electronic format + Int_t mChannel; ///< TOF channel index + uint16_t mTDC; ///< TDC bin number + uint16_t mTOT; ///< TOT bin number + InteractionRecord mIR{0, 0}; ///< InteractionRecord (orbit and bc) when digit occurs + Int_t mLabel; ///< Index of the corresponding entry in the MC label array + Double_t mCalibratedTime; //!< time of the digits after calibration (not persistent; it will be filled during clusterization) + Int_t mElectronIndex; //!/< index in electronic format uint32_t mTriggerOrbit = 0; //!< orbit id of trigger event // RS: orbit must be 32bits long uint16_t mTriggerBunch = 0; //!< bunch id of trigger event Bool_t mIsUsedInCluster; //!/< flag to declare that the digit was used to build a cluster 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/CalibTOFapi.cxx b/Detectors/TOF/base/src/CalibTOFapi.cxx index 281498990a9dd..fdc028bde536c 100644 --- a/Detectors/TOF/base/src/CalibTOFapi.cxx +++ b/Detectors/TOF/base/src/CalibTOFapi.cxx @@ -11,11 +11,40 @@ #include "TOFBase/CalibTOFapi.h" #include // for LOG +#include using namespace o2::tof; ClassImp(o2::tof::CalibTOFapi); +o2::tof::Diagnostic CalibTOFapi::doDRMerrCalibFromQCHisto(const TH2F* histo, const char* file_output_name) +{ + // this is a method which translate the QC output in qc/TOF/MO/TaskRaw/DRMCounter (TH2F) into a Diagnotic object for DRM (patter(crate, error), frequency) + // note that, differently from TRM errors, DRM ones are not stored in CTF by design (since very rare, as expected). Such an info is available only at the level of raw sync QC + o2::tof::Diagnostic drmDia; + + for (int j = 1; j <= Geo::kNCrate; j++) { + drmDia.fillDRM(j - 1, histo->GetBinContent(1, j)); + for (int i = 2; i <= histo->GetXaxis()->GetNbins(); i++) { + if (histo->GetBinContent(1, j)) { + if (histo->GetBinContent(i, j) > 0) { + drmDia.fillDRMerror(j - 1, i - 1, histo->GetBinContent(i, j)); + } + } + } + } + + TFile* fo = new TFile(file_output_name, "RECREATE"); + fo->WriteObjectAny(&drmDia, drmDia.Class_Name(), "ccdb_object"); + fo->Close(); + LOG(debug) << "DRM error ccdb object created in " << file_output_name << " with this content"; + drmDia.print(true); + + return drmDia; +} + +//______________________________________________________________________ + void CalibTOFapi::resetDia() { memset(mEmptyCrateProb, 0., Geo::kNCrate * 4); @@ -38,7 +67,7 @@ void CalibTOFapi::readActiveMap() { auto& mgr = CcdbManager::instance(); long timems = long(mTimeStamp) * 1000; - LOG(info) << "TOF get active map with timestamp (ms) = " << timems; + LOG(debug) << "TOF get active map with timestamp (ms) = " << timems; auto fee = mgr.getForTimeStamp("TOF/Calib/FEELIGHT", timems); loadActiveMap(fee); } @@ -116,11 +145,23 @@ void CalibTOFapi::readDiagnosticFrequencies() { auto& mgr = CcdbManager::instance(); long timems = long(mTimeStamp) * 1000; - LOG(info) << "TOF get Diagnostics with timestamp (ms) = " << timems; + LOG(info) << "TOF get TRM Diagnostics with timestamp (ms) = " << timems; mDiaFreq = mgr.getForTimeStamp("TOF/Calib/Diagnostic", timems); loadDiagnosticFrequencies(); } + +//______________________________________________________________________ + +void CalibTOFapi::readDiagnosticDRMFrequencies() +{ + auto& mgr = CcdbManager::instance(); + long timems = long(mTimeStamp) * 1000; + LOG(info) << "TOF get DRM Diagnostics with timestamp (ms) = " << timems; + mDiaFreq = mgr.getForTimeStamp("TOF/Calib/TRMerrors", timems); + + loadDiagnosticDRMFrequencies(); +} //______________________________________________________________________ void CalibTOFapi::loadDiagnosticFrequencies() @@ -210,6 +251,37 @@ void CalibTOFapi::loadDiagnosticFrequencies() //______________________________________________________________________ +void CalibTOFapi::loadDiagnosticDRMFrequencies() +{ + mDiaDRMFreq->print(); + + for (int ic = 0; ic < Geo::kNCrate; ic++) { // loop over crates + float DRMcounters = mDiaDRMFreq->getFrequencyDRM(ic); + + if (DRMcounters < 1) { + for (int ie = 0; ie < N_DRM_ERRORS; ie++) { + mErrorInDRM[ic][ie] = 0.; + } + continue; + } + + for (int ie = 0; ie < N_DRM_ERRORS; ie++) { // loop over error types + int ie_shifted = ie + DRM_ERRINDEX_SHIFT; + + float frequency = mDiaDRMFreq->getFrequencyDRMerror(ic, ie_shifted) * 1. / DRMcounters; // error frequency + if (frequency > 1) { + frequency = 1.; + } + if (frequency > 1E-6) { + LOG(debug) << "DRMmap: Crate = " << ic << " - error = " << ie << " - frequency = " << frequency; + } + mErrorInDRM[ic][ie] = frequency; + } + } +} + +//______________________________________________________________________ + void CalibTOFapi::writeLHCphase(LhcPhase* phase, std::map metadataLHCphase, uint64_t minTimeStamp, uint64_t maxTimeStamp) { @@ -330,6 +402,17 @@ void CalibTOFapi::resetTRMErrors() //______________________________________________________________________ +void CalibTOFapi::resetDRMErrors() +{ + for (auto index : mFillErrDRMChannel) { + mIsErrorDRMCh[index] = false; + } + + mFillErrDRMChannel.clear(); +} + +//______________________________________________________________________ + void CalibTOFapi::processError(int crate, int trm, int mask) { if (checkTRMPolicy(mask)) { // check the policy of TRM -> true=good TRM @@ -348,6 +431,32 @@ void CalibTOFapi::processError(int crate, int trm, int mask) //______________________________________________________________________ +void CalibTOFapi::processErrorDRM(int crate, int codeErr) +{ + int mask = 1 << codeErr; + + if (checkDRMPolicy(mask)) { + return; + } + + LOG(debug) << "DRMmask: crate = " << crate << " - mask = " << mask << " - critical mask = " << mDRMCriticalErrorMask; + + for (int trm = 3; trm < 13; trm++) { + int ech = (crate << 12) + ((trm - 3) << 8); + for (int i = ech; i < ech + 256; i++) { + int channel = Geo::getCHFromECH(i); + if (channel == -1 || mIsErrorDRMCh[channel] == true) { + continue; + } + + mIsErrorDRMCh[channel] = true; + mFillErrDRMChannel.push_back(channel); + } + } +} + +//______________________________________________________________________ + bool CalibTOFapi::checkTRMPolicy(int mask) const { return false; @@ -355,7 +464,25 @@ bool CalibTOFapi::checkTRMPolicy(int mask) const //______________________________________________________________________ +bool CalibTOFapi::checkDRMPolicy(int mask) const +{ + return !(mDRMCriticalErrorMask & mask); +} + +//______________________________________________________________________ + bool CalibTOFapi::isChannelError(int channel) const { return mIsErrorCh[channel]; } + +//______________________________________________________________________ + +bool CalibTOFapi::isChannelDRMError(int channel) const +{ + if (mIsErrorDRMCh[channel]) { + int detId[5]; + o2::tof::Geo::getVolumeIndices(channel, detId); + } + return mIsErrorDRMCh[channel]; +} diff --git a/Detectors/TOF/base/src/Geo.cxx b/Detectors/TOF/base/src/Geo.cxx index 08cda68c6d12e..7df4c3a3537e1 100644 --- a/Detectors/TOF/base/src/Geo.cxx +++ b/Detectors/TOF/base/src/Geo.cxx @@ -1049,8 +1049,8 @@ void Geo::rotateToSector(Float_t* xyz, Int_t isector) void Geo::alignedToNominalSector(Float_t* xyz, Int_t isector) { // rotate from the aligned sector frame coordinates to nominal ones (i.e. alpha=20*sector+10 deg.) - constexpr float CS[18] = {.848077e-01, 8.660254e-01, 6.427876e-01, 3.420202e-01, -4.371139e-08, -3.420201e-01, -6.427876e-01, -8.660254e-01, -9.848077e-01, -9.848077e-01, -8.660254e-01, -6.427875e-01, -3.420201e-01, 1.192488e-08, 3.420201e-01, 6.427875e-01, 8.660253e-01, 9.848078e-01}; - constexpr float SN[18] = {1.736482e-01, 5.000000e-01, 7.660444e-01, 9.396926e-01, 1.000000e+00, 9.396926e-01, 7.660444e-01, 5.000001e-01, 1.736483e-01, -1.736482e-01, -5.000000e-01, -7.660446e-01, -9.396927e-01, -1.000000e+00, -9.396926e-01, -7.660445e-01, -5.000002e-01, -1.736480e-01}; + constexpr float CS[18] = {+9.848078e-01, +8.660254e-01, +6.427876e-01, +3.420201e-01, +6.123234e-17, -3.420201e-01, -6.427876e-01, -8.660254e-01, -9.848078e-01, -9.848078e-01, -8.660254e-01, -6.427876e-01, -3.420201e-01, -1.836970e-16, +3.420201e-01, +6.427876e-01, +8.660254e-01, +9.848078e-01}; + constexpr float SN[18] = {+1.736482e-01, +5.000000e-01, +7.660444e-01, +9.396926e-01, +1.000000e+00, +9.396926e-01, +7.660444e-01, +5.000000e-01, +1.736482e-01, -1.736482e-01, -5.000000e-01, -7.660444e-01, -9.396926e-01, -1.000000e+00, -9.396926e-01, -7.660444e-01, -5.000000e-01, -1.736482e-01}; Float_t xyzDummy[3] = {xyz[1], xyz[2], xyz[0]}; // go to twisted coordinates... o2::tof::Geo::antiRotateToSector(xyzDummy, isector); // lab coordinates xyz[0] = xyzDummy[0] * CS[isector] + xyzDummy[1] * SN[isector]; 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/LHCClockCalibrator.h b/Detectors/TOF/calibration/include/TOFCalibration/LHCClockCalibrator.h index aaab8a06e5e86..4c8f5cdae8654 100644 --- a/Detectors/TOF/calibration/include/TOFCalibration/LHCClockCalibrator.h +++ b/Detectors/TOF/calibration/include/TOFCalibration/LHCClockCalibrator.h @@ -99,6 +99,8 @@ class LHCClockCalibrator final : public o2::calibration::TimeSlotCalibration& 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/LHCClockCalibrator.cxx b/Detectors/TOF/calibration/src/LHCClockCalibrator.cxx index c4fdc25e98849..5a9dd727cda8e 100644 --- a/Detectors/TOF/calibration/src/LHCClockCalibrator.cxx +++ b/Detectors/TOF/calibration/src/LHCClockCalibrator.cxx @@ -158,7 +158,7 @@ void LHCClockCalibrator::finalizeSlot(Slot& slot) l.setStartValidity(starting); l.setEndValidity(stopping); - mInfoVector.emplace_back("TOF/Calib/LHCphase", clName, flName, md, starting, stopping); + mInfoVector.emplace_back(mPath.Data(), clName, flName, md, starting, stopping); mLHCphaseVector.emplace_back(l); slot.print(); 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/LHCClockCalibratorSpec.h b/Detectors/TOF/calibration/testWorkflow/LHCClockCalibratorSpec.h index f2d3df9218249..1e3a6602939bf 100644 --- a/Detectors/TOF/calibration/testWorkflow/LHCClockCalibratorSpec.h +++ b/Detectors/TOF/calibration/testWorkflow/LHCClockCalibratorSpec.h @@ -53,7 +53,10 @@ class LHCClockCalibDevice : public o2::framework::Task int nb = std::max(500, ic.options().get("nbins")); auto slotL = ic.options().get("tf-per-slot"); auto delay = ic.options().get("max-delay"); + std::string path = ic.options().get("output-path"); + mCalibrator = std::make_unique(minEnt, nb); + mCalibrator->setPath(path.data()); mCalibrator->setSlotLength(slotL); mCalibrator->setMaxSlotsDelay(delay); @@ -216,6 +219,7 @@ DataProcessorSpec getLHCClockCalibDeviceSpec(bool useCCDB) AlgorithmSpec{adaptFromTask(ccdbRequest, useCCDB)}, Options{ {"tf-per-slot", VariantType::UInt32, 5u, {"number of TFs per calibration time slot"}}, + {"output-path", VariantType::String, "TOF/Calib/LHCphaseSync", {"path to ccdb output"}}, {"max-delay", VariantType::UInt32, 3u, {"number of slots in past to consider"}}, {"min-entries", VariantType::Int, 500, {"minimum number of entries to fit single time slot"}}, {"nbins", VariantType::Int, 4000, {"number of bins for "}}}}; 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/TOF/prototyping/CMakeLists.txt b/Detectors/TOF/prototyping/CMakeLists.txt index 1ce2268f1358a..7dfc9f8cb7361 100644 --- a/Detectors/TOF/prototyping/CMakeLists.txt +++ b/Detectors/TOF/prototyping/CMakeLists.txt @@ -32,6 +32,16 @@ o2_add_test_root_macro(findLabels.C O2::TOFBase LABELS tof) +o2_add_test_root_macro(makeDRMobj_tof.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsTOF + O2::TOFBase + LABELS tof) + +o2_add_test_root_macro(checkDRMobj_tof.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsTOF + O2::TOFBase + LABELS tof) + o2_add_test_root_macro(findTOFclusterFromLabel.C PUBLIC_LINK_LIBRARIES O2::DataFormatsTOF O2::SimulationDataFormat @@ -59,3 +69,8 @@ o2_add_test_root_macro(macroEvTime.C PUBLIC_LINK_LIBRARIES O2::TOFBase O2::TOFReconstruction LABELS tof) + +install( + FILES makeDRMobj_tof.C + checkDRMobj_tof.C + DESTINATION share/macro/) diff --git a/Detectors/TOF/prototyping/checkDRMobj_tof.C b/Detectors/TOF/prototyping/checkDRMobj_tof.C new file mode 100644 index 0000000000000..81381852b15df --- /dev/null +++ b/Detectors/TOF/prototyping/checkDRMobj_tof.C @@ -0,0 +1,42 @@ +// 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 "TFile.h" +#include "TH2F.h" +#include "TCanvas.h" +#include "TOFBase/CalibTOFapi.h" +#endif + +void checkDRMobj_tof(const char* fname = "ccdb.root") +{ + TFile* f = new TFile(fname); + + TH2F* hErrors = new TH2F("hDRMerrors", ";error code; frequency", 30, 0, 30, 72, 0, 72); + + o2::tof::Diagnostic* drmDia = (o2::tof::Diagnostic*)f->Get("ccdb_object"); + + for (int j = 1; j <= 72; j++) { + uint32_t patternRDH = o2::tof::Diagnostic::getDRMKey(j - 1); + for (int i = 1; i <= hErrors->GetXaxis()->GetNbins(); i++) { + uint32_t pattern = o2::tof::Diagnostic::getDRMerrorKey(j - 1, i - 1); + if (drmDia->getFrequency(patternRDH)) { + hErrors->SetBinContent(i, j, drmDia->getFrequency(pattern) * 1. / drmDia->getFrequency(patternRDH)); + } + } + } + + TCanvas* c = new TCanvas(); + c->cd(1); + hErrors->Draw("colz"); + + drmDia->print(true); +} diff --git a/Detectors/TOF/prototyping/makeDRMobj_tof.C b/Detectors/TOF/prototyping/makeDRMobj_tof.C new file mode 100644 index 0000000000000..2ae79d501b369 --- /dev/null +++ b/Detectors/TOF/prototyping/makeDRMobj_tof.C @@ -0,0 +1,45 @@ +// 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 "TFile.h" +#include "TH2F.h" +#include "TOFBase/CalibTOFapi.h" +#endif + +void makeDRMobj_tof(const char* inputfile = "TObject_1764607157510.root", bool dummy = false) +{ + if (dummy) { + o2::tof::Diagnostic drmDia; + for (int j = 1; j <= 72; j++) { + drmDia.fill(o2::tof::Diagnostic::getDRMKey(j - 1)); + } + + TFile* fo = new TFile("ccdb.root", "RECREATE"); + fo->WriteObjectAny(&drmDia, drmDia.Class_Name(), "ccdb_object"); + fo->Close(); + + return; + } + + TFile* f = new TFile(inputfile); + TH2F* h = (TH2F*)f->Get("ccdb_object"); + + float maxVal = h->GetBinContent(h->GetMaximumBin()); + + if (maxVal > 1E6) { // to avoid to pass value causing overflow + h->Scale(1E6 / maxVal); + } + + o2::tof::Diagnostic drmDia; + + drmDia = o2::tof::CalibTOFapi::doDRMerrCalibFromQCHisto(h, "ccdb.root"); +} diff --git a/Detectors/TOF/reconstruction/include/TOFReconstruction/CTFCoder.h b/Detectors/TOF/reconstruction/include/TOFReconstruction/CTFCoder.h index e559dcce7a1da..4ae99bae86eba 100644 --- a/Detectors/TOF/reconstruction/include/TOFReconstruction/CTFCoder.h +++ b/Detectors/TOF/reconstruction/include/TOFReconstruction/CTFCoder.h @@ -31,10 +31,10 @@ namespace o2 namespace tof { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TOF) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TOF, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode clusters to buffer with CTF diff --git a/Detectors/TOF/reconstruction/src/Clusterer.cxx b/Detectors/TOF/reconstruction/src/Clusterer.cxx index 0b393bfd45e78..dbfa472ce4112 100644 --- a/Detectors/TOF/reconstruction/src/Clusterer.cxx +++ b/Detectors/TOF/reconstruction/src/Clusterer.cxx @@ -54,11 +54,9 @@ void Clusterer::calibrateStrip() // LOG(info) << "channel = " << dig->getChannel(); dig->setBC(dig->getBC() - mBCOffset); // RS Don't use raw BC, always start from the beginning of the TF double calib = mCalibApi->getTimeCalibration(dig->getChannel(), dig->getTOT() * Geo::TOTBIN_NS); - //printf("channel %d) isProblematic = %d, fractionUnderPeak = %f\n",dig->getChannel(),mCalibApi->isProblematic(dig->getChannel()),mCalibApi->getFractionUnderPeak(dig->getChannel())); // toberem bool isProbOrError = mAreCalibStored ? mCalibApi->isChannelError(dig->getChannel()) || mCalibApi->isNoisy(dig->getChannel()) : mCalibApi->isChannelError(dig->getChannel()) || mCalibApi->isNoisy(dig->getChannel()) || mCalibApi->isProblematic(dig->getChannel()); dig->setIsProblematic(isProbOrError); - dig->setCalibratedTime(dig->getTDC() * Geo::TDCBIN + dig->getBC() * o2::constants::lhc::LHCBunchSpacingNS * 1E3 - Geo::LATENCYWINDOW * 1E3 - calib); //TODO: to be checked that "-" is correct, and we did not need "+" instead :-) - //printf("calibration correction = %f\n",calib); // toberem + dig->setCalibratedTime(dig->getTDC() * Geo::TDCBIN + dig->getBC() * o2::constants::lhc::LHCBunchSpacingNS * 1E3 - Geo::LATENCYWINDOW * 1E3 - calib); // TODO: to be checked that "-" is correct, and we did not need "+" instead :-) } } diff --git a/Detectors/TOF/simulation/include/TOFSimulation/Digitizer.h b/Detectors/TOF/simulation/include/TOFSimulation/Digitizer.h index 5153f168f176f..4e369cecf6e26 100644 --- a/Detectors/TOF/simulation/include/TOFSimulation/Digitizer.h +++ b/Detectors/TOF/simulation/include/TOFSimulation/Digitizer.h @@ -148,7 +148,10 @@ class Digitizer : public WindowFiller float mTotLastHit[10]; Int_t mXLastShift[10]; Int_t mZLastShift[10]; - ClassDefNV(Digitizer, 1); + + float mIsCrateRDHerr[Geo::kNCrate]; + + ClassDefNV(Digitizer, 2); }; } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/simulation/src/Detector.cxx b/Detectors/TOF/simulation/src/Detector.cxx index 2bfb76613dae5..97d5e03851291 100644 --- a/Detectors/TOF/simulation/src/Detector.cxx +++ b/Detectors/TOF/simulation/src/Detector.cxx @@ -637,7 +637,6 @@ void Detector::makeStripsInModules(Float_t ytof, Float_t zlenA) const // Define MRPC strip volume, called FSTR // Insert FSTR volume in FLTA/B/C volumes // - // ciao Float_t yFLT = ytof * 0.5 - Geo::MODULEWALLTHICKNESS; ///////////////// Detector itself ////////////////////// diff --git a/Detectors/TOF/simulation/src/Digitizer.cxx b/Detectors/TOF/simulation/src/Digitizer.cxx index ec899bd35fbff..e2c4fdcca9abe 100644 --- a/Detectors/TOF/simulation/src/Digitizer.cxx +++ b/Detectors/TOF/simulation/src/Digitizer.cxx @@ -95,7 +95,7 @@ int Digitizer::process(const std::vector* hits, std::vector* dig const double max_hit_time = TOFSimParams::Instance().max_hit_time; // hits array of TOF hits for a given simulated event - // digits passed from external to be filled, in continuous readout mode we will push it on mDigitsPerTimeFrame vector of vectors of digits + // digits passed from external to be filled, in continuous readout mode we will push it on mDigitsPerTimeFrame, final vector of digits // printf("process event time = %f with %ld hits\n",mEventTime.getTimeNS(),hits->size()); @@ -891,6 +891,7 @@ void Digitizer::fillOutputContainer(std::vector& digits) // fill diagnostics mCalibApi->resetTRMErrors(); + mCalibApi->resetDRMErrors(); float p = gRandom->Rndm(); if (mCalibApi->getEmptyTOFProb() > p) { // check empty TOF for (int i = 0; i < Geo::kNCrate; i++) { @@ -906,6 +907,14 @@ void Digitizer::fillOutputContainer(std::vector& digits) info.setEmptyCrate(i); isEmptyCrate[i] = true; } else { // check if filling diagnostic (noisy will be masked in clusterization, then skip here) + // Fill DRM RDH errors + for (int ie = 0; ie < mCalibApi->N_DRM_ERRORS; ie++) { + p = gRandom->Rndm(); + if (mCalibApi->getDRMprobError(i, ie) > p) { + mCalibApi->processErrorDRM(i, ie); + } + } + isEmptyCrate[i] = false; int slotreached = -1; const std::vector>& trmProg = mCalibApi->getTRMerrorProb(); @@ -955,7 +964,7 @@ void Digitizer::fillOutputContainer(std::vector& digits) for (auto [key, dig] : dmap) { int crate = Geo::getCrateFromECH(Geo::getECHFromCH(dig.getChannel())); - if (isEmptyCrate[crate] || mCalibApi->isChannelError(dig.getChannel())) { + if (isEmptyCrate[crate] || mCalibApi->isChannelError(dig.getChannel()) || mCalibApi->isChannelDRMError(dig.getChannel())) { // flag digits to be removed keyToBeRemoved.push_back(key); } diff --git a/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyDecoderSpec.h b/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyDecoderSpec.h index c09aa6abc9f7b..714b23d955a78 100644 --- a/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyDecoderSpec.h +++ b/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyDecoderSpec.h @@ -29,7 +29,7 @@ namespace tof class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +43,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyEncoderSpec.h b/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyEncoderSpec.h index ee0739c076597..27377b6447d1c 100644 --- a/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyEncoderSpec.h +++ b/Detectors/TOF/workflow/include/TOFWorkflowUtils/EntropyEncoderSpec.h @@ -29,7 +29,7 @@ namespace tof class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/workflow/src/EntropyDecoderSpec.cxx b/Detectors/TOF/workflow/src/EntropyDecoderSpec.cxx index 400914c64021f..8c0445e3ee3cb 100644 --- a/Detectors/TOF/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/TOF/workflow/src/EntropyDecoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace tof { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -93,7 +92,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"digitheader"}, o2::header::gDataOriginTOF, "DIGITHEADER", 0, Lifetime::Timeframe}, @@ -105,17 +104,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_TOF", "TOF", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_TOF", "TOF", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TOF/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_TOF", "TOF", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TOF/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "tof-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/workflow/src/EntropyEncoderSpec.cxx b/Detectors/TOF/workflow/src/EntropyEncoderSpec.cxx index 3fc47955f53c0..27d7c162cf670 100644 --- a/Detectors/TOF/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/TOF/workflow/src/EntropyEncoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace tof { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -71,13 +70,16 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("compDigits", o2::header::gDataOriginTOF, "DIGITS", 0, Lifetime::Timeframe); inputs.emplace_back("patterns", o2::header::gDataOriginTOF, "PATTERNS", 0, Lifetime::Timeframe); inputs.emplace_back("ROframes", o2::header::gDataOriginTOF, "READOUTWINDOW", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "TOF", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TOF/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "TOF", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TOF/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -86,14 +88,12 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{o2::header::gDataOriginTOF, "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "TOF", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}, {"irframe-shift", VariantType::Int, o2::tof::Geo::LATENCYWINDOW_IN_BC, {"IRFrame shift to account for latency"}}, {"mem-factor", VariantType::Float, 1.f, {"Memory allocation margin factor"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace tof } // namespace o2 diff --git a/Detectors/TOF/workflow/src/entropy-encoder-workflow.cxx b/Detectors/TOF/workflow/src/entropy-encoder-workflow.cxx index 1969672ad3fa3..547b3235ca684 100644 --- a/Detectors/TOF/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/TOF/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::tof::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::tof::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/TPC/CMakeLists.txt b/Detectors/TPC/CMakeLists.txt index e3de1ca57c1be..aea0dee361874 100644 --- a/Detectors/TPC/CMakeLists.txt +++ b/Detectors/TPC/CMakeLists.txt @@ -10,6 +10,7 @@ # or submit itself to any jurisdiction. add_subdirectory(base) +add_subdirectory(baserecsim) add_subdirectory(reconstruction) add_subdirectory(calibration) add_subdirectory(simulation) diff --git a/Detectors/TPC/base/CMakeLists.txt b/Detectors/TPC/base/CMakeLists.txt index d4c1bc4602d54..6456207e50530 100644 --- a/Detectors/TPC/base/CMakeLists.txt +++ b/Detectors/TPC/base/CMakeLists.txt @@ -12,7 +12,6 @@ o2_add_library(TPCBase SOURCES src/CalArray.cxx src/CalDet.cxx - src/CDBInterface.cxx src/ContainerFactory.cxx src/CRU.cxx src/DigitPos.cxx @@ -24,7 +23,6 @@ o2_add_library(TPCBase src/PadRegionInfo.cxx src/PadROCPos.cxx src/PadSecPos.cxx - src/Painter.cxx src/ParameterDetector.cxx src/ParameterElectronics.cxx src/ParameterGas.cxx @@ -37,7 +35,6 @@ o2_add_library(TPCBase src/CRUCalibHelpers.cxx src/IonTailSettings.cxx src/FEEConfig.cxx - src/DeadChannelMapCreator.cxx src/CommonModeCorrection.cxx PUBLIC_LINK_LIBRARIES Vc::Vc Boost::boost O2::DataFormatsTPC O2::DetectorsRaw O2::CCDB FairRoot::Base) @@ -45,7 +42,6 @@ o2_add_library(TPCBase o2_target_root_dictionary(TPCBase HEADERS include/TPCBase/CalArray.h include/TPCBase/CalDet.h - include/TPCBase/CDBInterface.h include/TPCBase/ContainerFactory.h include/TPCBase/CRU.h include/TPCBase/DigitPos.h @@ -57,7 +53,6 @@ o2_target_root_dictionary(TPCBase include/TPCBase/PadRegionInfo.h include/TPCBase/PadROCPos.h include/TPCBase/PadSecPos.h - include/TPCBase/Painter.h include/TPCBase/ParameterDetector.h include/TPCBase/ParameterElectronics.h include/TPCBase/ParameterGas.h @@ -70,22 +65,13 @@ o2_target_root_dictionary(TPCBase include/TPCBase/CRUCalibHelpers.h include/TPCBase/IonTailSettings.h include/TPCBase/FEEConfig.h - include/TPCBase/DeadChannelMapCreator.h - include/TPCBase/CommonModeCorrection.h - include/TPCBase/CDBTypes.h) + include/TPCBase/CommonModeCorrection.h) o2_add_test(Base COMPONENT_NAME tpc PUBLIC_LINK_LIBRARIES O2::TPCBase SOURCES test/testTPCBase.cxx LABELS tpc) -o2_add_test(CalDet - COMPONENT_NAME tpc - PUBLIC_LINK_LIBRARIES O2::TPCBase - SOURCES test/testTPCCalDet.cxx - ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage - LABELS tpc) - o2_add_test(Mapper COMPONENT_NAME tpc PUBLIC_LINK_LIBRARIES O2::TPCBase diff --git a/Detectors/TPC/base/include/TPCBase/CalArray.h b/Detectors/TPC/base/include/TPCBase/CalArray.h index 2679eae4e706e..c0d5a14bd86de 100644 --- a/Detectors/TPC/base/include/TPCBase/CalArray.h +++ b/Detectors/TPC/base/include/TPCBase/CalArray.h @@ -26,6 +26,11 @@ #include #endif +#ifdef NDEBUG +#undef NDEBUG +#include +#endif + namespace o2 { namespace tpc @@ -93,7 +98,11 @@ class CalArray int getPadSubsetNumber() const { return mPadSubsetNumber; } void setValue(const size_t channel, const T& value) { mData[channel] = value; } - const T getValue(const size_t channel) const { return mData[channel]; } + const T getValue(const size_t channel) const + { + assert(channel < mData.size()); + return mData[channel]; + } void setValue(const size_t row, const size_t pad, const T& value); const T getValue(const size_t row, const size_t pad) const; diff --git a/Detectors/TPC/base/include/TPCBase/CalDet.h b/Detectors/TPC/base/include/TPCBase/CalDet.h index cab1bd5757f27..76bbeaf8bebd1 100644 --- a/Detectors/TPC/base/include/TPCBase/CalDet.h +++ b/Detectors/TPC/base/include/TPCBase/CalDet.h @@ -30,6 +30,12 @@ #include "Rtypes.h" #endif +#ifndef NDEBUG +#undef NDEBUG +// always enable assert +#include +#endif + namespace o2 { namespace tpc @@ -211,7 +217,26 @@ inline const T CalDet::getValue(const ROC roc, const size_t row, const size_t } case PadSubset::Region: { const auto globalRow = roc.isOROC() ? mappedRow + mapper.getNumberOfRowsROC(ROC(0)) : mappedRow; - return mData[Mapper::REGION[globalRow] + roc.getSector() * Mapper::NREGIONS].getValue(Mapper::OFFSETCRUGLOBAL[globalRow] + mappedPad); + const auto dataRow = Mapper::REGION[globalRow] + roc.getSector() * Mapper::NREGIONS; + const auto index = Mapper::OFFSETCRUGLOBAL[globalRow] + mappedPad; + assert(dataRow < mData.size()); + if (index >= mData[dataRow].getData().size()) { + // S. Wenzel: We shouldn't come here but we do. For instance for CalDet calibrations loaded from + // creator.loadIDCPadFlags(1731274461770); + + // In this case there is an index overflow, leading to invalid reads and potentially a segfault. + // To increase stability, for now returning a trivial answer. This can be removed once either the algorithm + // or the calibration data has been fixed. +#ifndef GPUCA_ALIGPUCODE // hide from GPU standalone compilation + static bool printMsg = true; + if (printMsg) { + LOG(error) << "Out of bound access in TPC CalDet ROC " << roc << " row " << row << " pad " << pad << " (no more messages printed)"; + } + printMsg = false; +#endif + return T{}; + } + return mData[dataRow].getValue(index); break; } } 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/ParameterGEM.h b/Detectors/TPC/base/include/TPCBase/ParameterGEM.h index 2d55a550764ac..cb458fbb5dafa 100644 --- a/Detectors/TPC/base/include/TPCBase/ParameterGEM.h +++ b/Detectors/TPC/base/include/TPCBase/ParameterGEM.h @@ -54,6 +54,7 @@ struct ParameterGEM : public o2::conf::ConfigurableParamHelper { float AbsoluteGain[4] = {14.f, 8.f, 53.f, 240.f}; ///< Absolute gain float CollectionEfficiency[4] = {1.f, 0.2f, 0.25f, 1.f}; ///< Collection efficiency float ExtractionEfficiency[4] = {0.65f, 0.55f, 0.12f, 0.6f}; ///< Extraction efficiency + float RelativeGainStack[4] = {1.f, 1.f, 1.f, 1.f}; ///< Relative gain of the stack per region (IROC, OROC1, OROC2, OROC3) for the EffectiveMode float TotalGainStack = 2000.f; ///< Total gain of the stack for the EffectiveMode float KappaStack = 1.205f; ///< Variable steering the energy resolution of the full stack for the EffectiveMode float EfficiencyStack = 0.528f; ///< Variable steering the single electron efficiency of the full stack for the EffectiveMode 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/base/src/TPCBaseLinkDef.h b/Detectors/TPC/base/src/TPCBaseLinkDef.h index 60924db3953e2..2b7a7ff19542d 100644 --- a/Detectors/TPC/base/src/TPCBaseLinkDef.h +++ b/Detectors/TPC/base/src/TPCBaseLinkDef.h @@ -27,13 +27,9 @@ #pragma link C++ class o2::tpc::CalDet < unsigned> + ; #pragma link C++ class o2::tpc::CalDet < short> + ; #pragma link C++ class o2::tpc::CalDet < bool> + ; -#pragma link C++ class o2::tpc::CalDet < o2::tpc::PadFlags> + ; #pragma link C++ class std::vector < o2::tpc::CalDet < float>> + ; #pragma link C++ class std::vector < o2::tpc::CalDet < float>*> + ; #pragma link C++ class std::unordered_map < std::string, o2::tpc::CalDet < float>> + ; -#pragma link C++ enum o2::tpc::CDBType; -#pragma link C++ class o2::tpc::CDBInterface; -#pragma link C++ class o2::tpc::CDBStorage; #pragma link C++ class o2::tpc::ContainerFactory; #pragma link C++ class o2::tpc::CRU; #pragma link C++ class o2::tpc::DigitPos; @@ -49,8 +45,6 @@ #pragma link C++ class o2::tpc::ROC; #pragma link C++ class o2::tpc::Sector; -#pragma link C++ class o2::tpc::painter + ; - // #pragma link C++ class std::vector + ; #pragma link C++ class o2::tpc::ParameterDetector; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::tpc::ParameterDetector> + ; @@ -89,5 +83,4 @@ #pragma link C++ function o2::tpc::cru_calib_helpers::getCalPad < 2>(const std::string_view, const std::string_view, std::string_view) #pragma link C++ function o2::tpc::cru_calib_helpers::getCalPad < 6>(const std::string_view, const std::string_view, std::string_view) -#pragma link C++ class o2::tpc::DeadChannelMapCreator + ; #endif diff --git a/Detectors/TPC/base/test/testTPCCDBInterface.cxx b/Detectors/TPC/base/test/testTPCCDBInterface.cxx index 3074c5e90a00c..a0f4142b3f807 100644 --- a/Detectors/TPC/base/test/testTPCCDBInterface.cxx +++ b/Detectors/TPC/base/test/testTPCCDBInterface.cxx @@ -21,8 +21,7 @@ #include "TFile.h" // o2 includes -#include "TPCBase/CDBInterface.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/CalArray.h" #include "TPCBase/CalDet.h" #include "TPCBase/Mapper.h" diff --git a/Detectors/TPC/baserecsim/CMakeLists.txt b/Detectors/TPC/baserecsim/CMakeLists.txt new file mode 100644 index 0000000000000..b6c0f2644aa81 --- /dev/null +++ b/Detectors/TPC/baserecsim/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright 2019-2025 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(TPCBaseRecSim + SOURCES src/DeadChannelMapCreator.cxx + src/Painter.cxx + src/CDBInterface.cxx + PUBLIC_LINK_LIBRARIES O2::TPCBase) + +o2_target_root_dictionary(TPCBaseRecSim + EXTRA_PATCH src/TPCFlagsMemberCustomStreamer.cxx + HEADERS include/TPCBaseRecSim/Painter.h + include/TPCBaseRecSim/PadFlags.h + include/TPCBaseRecSim/DeadChannelMapCreator.h + include/TPCBaseRecSim/CDBTypes.h + include/TPCBaseRecSim/CDBInterface.h) + +if(BUILD_SIMULATION) + # this test needs CCDB/XROOTD which is for sure + # available in the default-o2 software stack + o2_add_test(CalDet + COMPONENT_NAME tpc + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim + SOURCES test/testTPCCalDet.cxx + ENVIRONMENT O2_ROOT=${CMAKE_BINARY_DIR}/stage + LABELS tpc) +endif() diff --git a/Detectors/TPC/base/include/TPCBase/CDBInterface.h b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/CDBInterface.h similarity index 99% rename from Detectors/TPC/base/include/TPCBase/CDBInterface.h rename to Detectors/TPC/baserecsim/include/TPCBaseRecSim/CDBInterface.h index 4c28744f0378a..5b2c8e6d48251 100644 --- a/Detectors/TPC/base/include/TPCBase/CDBInterface.h +++ b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/CDBInterface.h @@ -25,8 +25,8 @@ #include "CCDB/CcdbApi.h" #include "TPCBase/CalDet.h" #include "TPCBase/FEEConfig.h" -#include "TPCBase/CDBTypes.h" -#include "TPCBase/DeadChannelMapCreator.h" +#include "TPCBaseRecSim/CDBTypes.h" +#include "TPCBaseRecSim/DeadChannelMapCreator.h" #include "DataFormatsTPC/LtrCalibData.h" #include "DataFormatsTPC/Defs.h" #include "CommonUtils/NameConf.h" diff --git a/Detectors/TPC/base/include/TPCBase/CDBTypes.h b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/CDBTypes.h similarity index 100% rename from Detectors/TPC/base/include/TPCBase/CDBTypes.h rename to Detectors/TPC/baserecsim/include/TPCBaseRecSim/CDBTypes.h diff --git a/Detectors/TPC/base/include/TPCBase/DeadChannelMapCreator.h b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/DeadChannelMapCreator.h similarity index 98% rename from Detectors/TPC/base/include/TPCBase/DeadChannelMapCreator.h rename to Detectors/TPC/baserecsim/include/TPCBaseRecSim/DeadChannelMapCreator.h index 9d4317380f4bc..5a3fc38aa208b 100644 --- a/Detectors/TPC/base/include/TPCBase/DeadChannelMapCreator.h +++ b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/DeadChannelMapCreator.h @@ -21,8 +21,8 @@ #include "CCDB/CcdbApi.h" -#include "DataFormatsTPC/Defs.h" -#include "TPCBase/CDBTypes.h" +#include "TPCBaseRecSim/PadFlags.h" +#include "TPCBaseRecSim/CDBTypes.h" #include "TPCBase/CalDet.h" #include "TPCBase/FEEConfig.h" diff --git a/Detectors/TPC/baserecsim/include/TPCBaseRecSim/PadFlags.h b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/PadFlags.h new file mode 100644 index 0000000000000..e13a24adf407e --- /dev/null +++ b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/PadFlags.h @@ -0,0 +1,44 @@ +// Copyright 2019-2025 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 Defs.h +/// @author Jens Wiechula, Jens.Wiechula@ikf.uni-frankfurt.de +/// + +/// @brief Global TPC definitions and constants + +#ifndef AliceO2_TPC_PadFlags_H +#define AliceO2_TPC_PadFlags_H + +namespace o2::tpc +{ + +enum class PadFlags : unsigned short { + flagGoodPad = 1 << 0, ///< flag for a good pad binary 0001 + flagDeadPad = 1 << 1, ///< flag for a dead pad binary 0010 + flagUnknownPad = 1 << 2, ///< flag for unknown status binary 0100 + flagSaturatedPad = 1 << 3, ///< flag for saturated status binary 0100 + flagHighPad = 1 << 4, ///< flag for pad with extremly high IDC value + flagLowPad = 1 << 5, ///< flag for pad with extremly low IDC value + flagSkip = 1 << 6, ///< flag for defining a pad which is just ignored during the calculation of I1 and IDCDelta + flagFEC = 1 << 7, ///< flag for a whole masked FEC + flagNeighbour = 1 << 8, ///< flag if n neighbouring pads are outlier + flagAllNoneGood = flagDeadPad | flagUnknownPad | flagSaturatedPad | flagHighPad | flagLowPad | flagSkip | flagFEC | flagNeighbour, +}; + +inline PadFlags operator&(PadFlags a, PadFlags b) { return static_cast(static_cast(a) & static_cast(b)); } +inline PadFlags operator~(PadFlags a) { return static_cast(~static_cast(a)); } +inline PadFlags operator|(PadFlags a, PadFlags b) { return static_cast(static_cast(a) | static_cast(b)); } + +} // namespace o2::tpc + +#endif diff --git a/Detectors/TPC/base/include/TPCBase/Painter.h b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/Painter.h similarity index 97% rename from Detectors/TPC/base/include/TPCBase/Painter.h rename to Detectors/TPC/baserecsim/include/TPCBaseRecSim/Painter.h index 976fe2846ce0c..5cf8691635b1f 100644 --- a/Detectors/TPC/base/include/TPCBase/Painter.h +++ b/Detectors/TPC/baserecsim/include/TPCBaseRecSim/Painter.h @@ -53,7 +53,8 @@ struct painter { enum class Type : int { Pad, ///< drawing pads Stack, ///< drawing stacks - FEC ///< drawing of FECs + FEC, ///< drawing of FECs + SCD, ///< drawing of FECs }; static std::array colors; @@ -87,8 +88,10 @@ struct painter { /// create a vector of FEC corner coordinates for one full sector static std::vector getFECCoordinatesSector(); + static std::vector getSCDY2XCoordinatesSector(std::string binningStr); + /// \return returns coordinates for given type - static std::vector getCoordinates(const Type type); + static std::vector getCoordinates(const Type type, std::string binningStr = ""); /// binning vector with radial pad-row positions (in cm) /// \param roc roc number (0-35 IROC, 36-71 OROC, >=72 full sector) @@ -143,11 +146,11 @@ struct painter { /// \param yMin minimum y coordinate of the histogram /// \param yMax maximum y coordinate of the histogram /// \param type granularity of the histogram (per pad or per stack) - static TH2Poly* makeSectorHist(const std::string_view name = "hSector", const std::string_view title = "Sector;local #it{x} (cm);local #it{y} (cm)", const float xMin = 83.65f, const float xMax = 247.7f, const float yMin = -43.7f, const float yMax = 43.7f, const Type type = Type::Pad); + static TH2Poly* makeSectorHist(const std::string_view name = "hSector", const std::string_view title = "Sector;local #it{x} (cm);local #it{y} (cm)", const float xMin = 83.65f, const float xMax = 247.7f, const float yMin = -43.7f, const float yMax = 43.7f, const Type type = Type::Pad, std::string binningStr = ""); /// make a side-wise histogram with correct pad corners /// \param type granularity of the histogram (per pad or per stack) - static TH2Poly* makeSideHist(Side side, const Type type = Type::Pad); + static TH2Poly* makeSideHist(Side side, const Type type = Type::Pad, std::string binningStr = ""); /// fill existing TH2Poly histogram for CalDet object /// \param h2D histogram to fill diff --git a/Detectors/TPC/base/src/CDBInterface.cxx b/Detectors/TPC/baserecsim/src/CDBInterface.cxx similarity index 99% rename from Detectors/TPC/base/src/CDBInterface.cxx rename to Detectors/TPC/baserecsim/src/CDBInterface.cxx index 06f6a360670dc..2aaf9c58cbe2c 100644 --- a/Detectors/TPC/base/src/CDBInterface.cxx +++ b/Detectors/TPC/baserecsim/src/CDBInterface.cxx @@ -28,7 +28,7 @@ // o2 includes #include "DataFormatsTPC/CalibdEdxCorrection.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/ParameterDetector.h" #include "TPCBase/ParameterElectronics.h" #include "TPCBase/ParameterGEM.h" diff --git a/Detectors/TPC/base/src/DeadChannelMapCreator.cxx b/Detectors/TPC/baserecsim/src/DeadChannelMapCreator.cxx similarity index 98% rename from Detectors/TPC/base/src/DeadChannelMapCreator.cxx rename to Detectors/TPC/baserecsim/src/DeadChannelMapCreator.cxx index 8c4e754fc5327..2d41e277b8583 100644 --- a/Detectors/TPC/base/src/DeadChannelMapCreator.cxx +++ b/Detectors/TPC/baserecsim/src/DeadChannelMapCreator.cxx @@ -14,8 +14,8 @@ #include #include "CommonUtils/NameConf.h" #include "Framework/Logger.h" -#include "TPCBase/DeadChannelMapCreator.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/DeadChannelMapCreator.h" +#include "TPCBaseRecSim/Painter.h" using namespace o2::tpc; diff --git a/Detectors/TPC/base/src/Painter.cxx b/Detectors/TPC/baserecsim/src/Painter.cxx similarity index 92% rename from Detectors/TPC/base/src/Painter.cxx rename to Detectors/TPC/baserecsim/src/Painter.cxx index 9f143d3fa45ce..a571b50607dd2 100644 --- a/Detectors/TPC/base/src/Painter.cxx +++ b/Detectors/TPC/baserecsim/src/Painter.cxx @@ -31,7 +31,9 @@ #include "TPaveText.h" #include "TPaletteAxis.h" #include "TObjArray.h" +#include "TMath.h" +#include "Algorithm/RangeTokenizer.h" #include "CommonUtils/StringUtils.h" #include "DataFormatsTPC/Defs.h" #include "TPCBase/ROC.h" @@ -39,7 +41,8 @@ #include "TPCBase/Mapper.h" #include "TPCBase/CalDet.h" #include "TPCBase/CalArray.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/PadFlags.h" #include "TPCBase/Utils.h" #include "DataFormatsTPC/LaserTrack.h" @@ -223,7 +226,77 @@ std::vector painter::getFECCoordinatesSector() return padCoords; } -std::vector painter::getCoordinates(const Type type) +std::vector painter::getSCDY2XCoordinatesSector(std::string binningStr) +{ + const float deadZone = 1.5; + const float secPhi = 20.0 * TMath::DegToRad(); + std::vector padCoords; + const Mapper& mapper = Mapper::instance(); + const auto nPadRows = Mapper::PADROWS; + std::vector maxY2X(nPadRows); + auto binCenters = o2::RangeTokenizer::tokenize(binningStr); + size_t nY2XBins = 20; + std::vector halfBinWidth; + + auto setUniformBinning = [&binCenters, &halfBinWidth](int nY2XBins) { + binCenters.resize(nY2XBins); + halfBinWidth.resize(nY2XBins); + for (int i = 0; i < nY2XBins; ++i) { + const auto binWidth = 2.f / nY2XBins; + halfBinWidth[i] = binWidth / 2.f; + binCenters[i] = -1.f + (i + 0.5f) * binWidth; + } + }; + + if (binCenters.size() == 0) { + LOGP(info, "Empty binning provided, will use default uniform y/x binning with {} bins", nY2XBins); + setUniformBinning(nY2XBins); + } else if (binCenters.size() == 1) { + nY2XBins = static_cast(binCenters.at(0)); + LOGP(info, "Setting uniform binning for y/x with {} bins", nY2XBins); + setUniformBinning(nY2XBins); + } else { + nY2XBins = binCenters.size() - 1; + if (std::abs(binCenters[0] + 1.f) > 1e-6 || std::abs(binCenters[nY2XBins] - 1.f) > 1e-6) { + LOG(error) << "Provided binning for y/x not in range -1 to 1: " << binCenters[0] << " - " << binCenters[nY2XBins] << ". Using default uniform binning with " << nY2XBins << " bins"; + setUniformBinning(nY2XBins); + } else { + LOGP(info, "Setting custom binning for y/x with {} bins", nY2XBins); + halfBinWidth.reserve(nY2XBins); + halfBinWidth.clear(); + for (int i = 0; i < nY2XBins; ++i) { + halfBinWidth.push_back(.5f * (binCenters[i + 1] - binCenters[i])); + binCenters[i] = .5f * (binCenters[i] + binCenters[i + 1]); + } + binCenters.resize(nY2XBins); + } + } + + for (int irow = 0; irow < nPadRows; ++irow) { + const auto x = mapper.getPadCentre(PadPos(irow, 0)).X(); + maxY2X[irow] = std::tan(.5f * secPhi) - deadZone / x; + const auto region = Mapper::REGION[irow]; + const auto ph = mapper.getPadRegionInfo(region).getPadHeight(); + const auto xPadBottom = x - ph / 2; + const auto xPadTop = x + ph / 2; + for (int iy2x = 0; iy2x < nY2XBins; ++iy2x) { + auto& padCoord = padCoords.emplace_back(); + float yPadRight = 0; + if (iy2x == 0) { + yPadRight = maxY2X[irow] * (binCenters[iy2x] - halfBinWidth[iy2x]); + } else { + yPadRight = maxY2X[irow] * (binCenters[iy2x - 1] + halfBinWidth[iy2x - 1]); + } + const auto yPadLeft = maxY2X[irow] * (binCenters[iy2x] + halfBinWidth[iy2x]); + padCoord.xVals = {xPadBottom, xPadTop, xPadTop, xPadBottom}; + padCoord.yVals = {yPadRight * xPadBottom, yPadRight * xPadTop, yPadLeft * xPadTop, yPadLeft * xPadBottom}; + } + } + + return padCoords; +} + +std::vector painter::getCoordinates(const Type type, std::string binningStr) { if (type == Type::Pad) { return painter::getPadCoordinatesSector(); @@ -231,6 +304,8 @@ std::vector painter::getCoordinates(const Type return painter::getStackCoordinatesSector(); } else if (type == Type::FEC) { return painter::getFECCoordinatesSector(); + } else if (type == Type::SCD) { + return painter::getSCDY2XCoordinatesSector(binningStr); } else { LOGP(warning, "Wrong Type provided!"); return std::vector(); @@ -291,6 +366,8 @@ TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float const Mapper& mapper = Mapper::instance(); + const bool draw1D = nbins1D > 0; + // ===| name and title |====================================================== std::string title = calDet.getName(); std::string name = calDet.getName(); @@ -305,11 +382,13 @@ TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float const int bufferSize = TH1::GetDefaultBufferSize(); TH1::SetDefaultBufferSize(Sector::MAXSECTOR * mapper.getPadsInSector()); - auto hAside1D = new TH1F(fmt::format("h_Aside_1D_{}", name).data(), fmt::format("{0} (A-Side);{0}", title).data(), - nbins1D, xMin1D, xMax1D); // TODO: modify ranges + auto hAside1D = draw1D ? new TH1F(fmt::format("h_Aside_1D_{}", name).data(), fmt::format("{0} (A-Side);{0}", title).data(), + nbins1D, xMin1D, xMax1D) + : nullptr; // TODO: modify ranges - auto hCside1D = new TH1F(fmt::format("h_Cside_1D_{}", name).data(), fmt::format("{0} (C-Side);{0}", title).data(), - nbins1D, xMin1D, xMax1D); // TODO: modify ranges + auto hCside1D = draw1D ? new TH1F(fmt::format("h_Cside_1D_{}", name).data(), fmt::format("{0} (C-Side);{0}", title).data(), + nbins1D, xMin1D, xMax1D) + : nullptr; // TODO: modify ranges auto hAside2D = new TH2F(fmt::format("h_Aside_2D_{}", name).data(), fmt::format("{0} (A-Side);#it{{x}} (cm);#it{{y}} (cm);{0}", title).data(), 330, -270, 270, 330, -270, 270); @@ -336,7 +415,9 @@ TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float if (!hist2D->GetBinContent(bin)) { hist2D->SetBinContent(bin, double(val)); } - hist1D->Fill(double(val)); + if (draw1D) { + hist1D->Fill(double(val)); + } } } } @@ -352,13 +433,13 @@ TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float gStyle->SetOptStat("mr"); auto c = outputCanvas; if (!c) { - c = new TCanvas(fmt::format("c_{}", name).data(), title.data(), 1000, 1000); + c = new TCanvas(fmt::format("c_{}", name).data(), title.data(), 1000, draw1D ? 1000 : 500); } gStyle->SetStatX(1. - gPad->GetRightMargin()); gStyle->SetStatY(1. - gPad->GetTopMargin()); c->Clear(); - c->Divide(2, 2); + c->Divide(2, draw1D ? 2 : 1); c->cd(1); hAside2D->Draw("colz"); @@ -376,18 +457,22 @@ TCanvas* painter::draw(const CalDet& calDet, int nbins1D, float xMin1D, float adjustPalette(hCside2D, 0.92); drawSectorsXY(Side::C); - c->cd(3); - hAside1D->Draw(); + if (draw1D) { + c->cd(3); + hAside1D->Draw(); - c->cd(4); - hCside1D->Draw(); + c->cd(4); + hCside1D->Draw(); + + // associate histograms to canvas + hAside1D->SetBit(TObject::kCanDelete); + hCside1D->SetBit(TObject::kCanDelete); + } // reset the buffer size TH1::SetDefaultBufferSize(bufferSize); // associate histograms to canvas - hAside1D->SetBit(TObject::kCanDelete); - hCside1D->SetBit(TObject::kCanDelete); hAside2D->SetBit(TObject::kCanDelete); hCside2D->SetBit(TObject::kCanDelete); @@ -795,11 +880,11 @@ std::vector painter::makeSummaryCanvases(const std::string_view fileNa } //______________________________________________________________________________ -TH2Poly* painter::makeSectorHist(const std::string_view name, const std::string_view title, const float xMin, const float xMax, const float yMin, const float yMax, const Type type) +TH2Poly* painter::makeSectorHist(const std::string_view name, const std::string_view title, const float xMin, const float xMax, const float yMin, const float yMax, const Type type, std::string binningStr) { auto poly = new TH2Poly(name.data(), title.data(), xMin, xMax, yMin, yMax); - auto coords = painter::getCoordinates(type); + auto coords = painter::getCoordinates(type, binningStr); for (const auto& coord : coords) { poly->AddBin(coord.xVals.size(), coord.xVals.data(), coord.yVals.data()); } @@ -808,12 +893,12 @@ TH2Poly* painter::makeSectorHist(const std::string_view name, const std::string_ } //______________________________________________________________________________ -TH2Poly* painter::makeSideHist(Side side, const Type type) +TH2Poly* painter::makeSideHist(Side side, const Type type, std::string binningStr) { const auto s = (side == Side::A) ? "A" : "C"; auto poly = new TH2Poly(fmt::format("hSide_{}", s).data(), fmt::format("{}-Side;#it{{x}} (cm);#it{{y}} (cm)", s).data(), -270., 270., -270., 270.); - auto coords = painter::getCoordinates(type); + auto coords = painter::getCoordinates(type, binningStr); for (int isec = 0; isec < 18; ++isec) { const float angDeg = 10.f + isec * 20; for (auto coord : coords) { diff --git a/Detectors/TPC/baserecsim/src/TPCBaseRecSimLinkDef.h b/Detectors/TPC/baserecsim/src/TPCBaseRecSimLinkDef.h new file mode 100644 index 0000000000000..37822e3c02669 --- /dev/null +++ b/Detectors/TPC/baserecsim/src/TPCBaseRecSimLinkDef.h @@ -0,0 +1,27 @@ +// Copyright 2019-2025 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. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ enum o2::tpc::PadFlags + ; // enum itself +#pragma link C++ class std::vector < o2::tpc::PadFlags> + ; +#pragma link C++ enum o2::tpc::CDBType; +#pragma link C++ class o2::tpc::CDBInterface; +#pragma link C++ class o2::tpc::CDBStorage; +#pragma link C++ class o2::tpc::CalArray < o2::tpc::PadFlags> + ; +#pragma link C++ class o2::tpc::CalDet < o2::tpc::PadFlags> + ; +#pragma link C++ class o2::tpc::painter + ; +#pragma link C++ class o2::tpc::DeadChannelMapCreator + ; +#endif diff --git a/Detectors/TPC/baserecsim/src/TPCFlagsMemberCustomStreamer.cxx b/Detectors/TPC/baserecsim/src/TPCFlagsMemberCustomStreamer.cxx new file mode 100644 index 0000000000000..27ebfeb3c64bb --- /dev/null +++ b/Detectors/TPC/baserecsim/src/TPCFlagsMemberCustomStreamer.cxx @@ -0,0 +1,80 @@ +// 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 "TPCBase/CalArray.h" +#include +#include +#include +#include + +// to enable assert statements +#ifdef NDEBUG +#undef NDEBUG +#include +#endif + +// The following code provides a specific ROOT I/O streaming method +// for the mData member of CalArray +// This member was written incorrectly to the TFile in ROOT versions < 6.36, causing +// segfaults when reading on ARM64 (occassionally). +// We continue to write it in the incorrect format and fix the reading back. + +// See also: +// - https://github.com/root-project/root/pull/17009 +// - https://its.cern.ch/jira/browse/O2-4671 + +void MemberVectorPadFlagsStreamer(TBuffer& R__b, void* objp, int n) +{ + if (n != 1) { + std::cerr << "Error in MemberVectorPadFlagsStreamer : Unexpected n " << n << std::endl; + return; + } + std::vector* obj = static_cast*>(objp); + if (R__b.IsReading()) { + obj->clear(); + std::vector R__stl; + R__stl.clear(); + int R__n; + R__b >> R__n; + R__stl.reserve(R__n); + for (int R__i = 0; R__i < R__n; R__i++) { + Int_t readtemp; + R__b >> readtemp; + R__stl.push_back(readtemp); + } + auto data = reinterpret_cast(R__stl.data()); + constexpr size_t bloatfactor = sizeof(int) / sizeof(o2::tpc::PadFlags); + for (int i = 0; i < bloatfactor * R__n; ++i) { + obj->push_back(static_cast(data[i])); + } + } else { + // We always save things with the old format. + R__b << (int)obj->size() / 2; + for (size_t i = 0; i < obj->size(); i++) { + R__b << (short)obj->at(i); + } + } +} + +// register the streamer via static global initialization (on library load) +// the streamer is only correct in combination with new ROOT +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 33, 00) +namespace ROOT +{ +static __attribute__((used)) int _R__dummyStreamer_3 = + ([]() { + if (!getenv("TPC_PADFLAGS_STREAMER_OFF")) { + ROOT::GenerateInitInstance((o2::tpc::CalArray *)nullptr)->AdoptMemberStreamer("mData", new TMemberStreamer(MemberVectorPadFlagsStreamer)); + } + return 0; + })(); +} // namespace ROOT +#endif diff --git a/Detectors/TPC/base/test/testTPCCalDet.cxx b/Detectors/TPC/baserecsim/test/testTPCCalDet.cxx similarity index 97% rename from Detectors/TPC/base/test/testTPCCalDet.cxx rename to Detectors/TPC/baserecsim/test/testTPCCalDet.cxx index b93e952084396..bf4cfddb780f0 100644 --- a/Detectors/TPC/base/test/testTPCCalDet.cxx +++ b/Detectors/TPC/baserecsim/test/testTPCCalDet.cxx @@ -24,6 +24,7 @@ #include "TPCBase/CalDet.h" #include "TFile.h" #include "Framework/TypeTraits.h" +#include "TPCBaseRecSim/DeadChannelMapCreator.h" namespace o2::tpc { @@ -344,4 +345,12 @@ BOOST_AUTO_TEST_CASE(CalDetTypeTest) BOOST_CHECK(testDict == true); } +BOOST_AUTO_TEST_CASE(CalDetStreamerTest) +{ + // simple code executing the TPC IDCPadFlags loading in a standalone env --> easy to valgrind + o2::tpc::DeadChannelMapCreator creator{}; + creator.init("https://alice-ccdb.cern.ch"); + creator.loadIDCPadFlags(1731274461770); +} + } // namespace o2::tpc diff --git a/Detectors/TPC/calibration/CMakeLists.txt b/Detectors/TPC/calibration/CMakeLists.txt index 8bcb3254edb32..6aeb497c1cf23 100644 --- a/Detectors/TPC/calibration/CMakeLists.txt +++ b/Detectors/TPC/calibration/CMakeLists.txt @@ -25,7 +25,6 @@ o2_add_library(TPCCalibration src/CalibPadGainTracksBase.cxx src/CalibLaserTracks.cxx src/LaserTracksCalibrator.cxx - src/NeuralNetworkClusterizer.cxx src/SACDecoder.cxx src/IDCAverageGroup.cxx src/IDCAverageGroupBase.cxx @@ -49,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 @@ -59,7 +58,10 @@ o2_add_library(TPCCalibration src/DigitAdd.cxx src/CorrectdEdxDistortions.cxx src/PressureTemperatureHelper.cxx - PUBLIC_LINK_LIBRARIES O2::DataFormatsTPC O2::TPCBase + src/CMVContainer.cxx + src/CorrectionMapsLoader.cxx + src/CMVHelper.cxx + PUBLIC_LINK_LIBRARIES O2::DataFormatsTPC O2::TPCBaseRecSim O2::TPCReconstruction ROOT::Minuit Microsoft.GSL::GSL O2::DetectorsCalibration @@ -84,7 +86,6 @@ o2_target_root_dictionary(TPCCalibration include/TPCCalibration/FastHisto.h include/TPCCalibration/CalibLaserTracks.h include/TPCCalibration/LaserTracksCalibrator.h - include/TPCCalibration/NeuralNetworkClusterizer.h include/TPCCalibration/SACDecoder.h include/TPCCalibration/IDCAverageGroup.h include/TPCCalibration/IDCAverageGroupBase.h @@ -117,19 +118,22 @@ 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::TPCBase + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim LABELS tpc) o2_add_test_root_macro(macro/drawNoiseAndPedestal.C - PUBLIC_LINK_LIBRARIES O2::TPCBase + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim LABELS tpc) o2_add_test_root_macro(macro/drawPulser.C - PUBLIC_LINK_LIBRARIES O2::TPCBase + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim LABELS tpc) o2_add_test_root_macro(macro/mergeNoiseAndPedestal.C - PUBLIC_LINK_LIBRARIES O2::TPCBase + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim LABELS tpc) o2_add_test_root_macro(macro/runPedestal.C PUBLIC_LINK_LIBRARIES O2::TPCCalibration @@ -155,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/CMakeLists.txt b/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt index 566558b7e982f..47bb9c09a9951 100644 --- a/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt +++ b/Detectors/TPC/calibration/SpacePoints/CMakeLists.txt @@ -8,6 +8,7 @@ # 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_compile_options(-O0 -g -fPIC) o2_add_library(SpacePoints SOURCES src/SpacePointsCalibParam.cxx @@ -28,7 +29,8 @@ o2_add_library(SpacePoints O2::DataFormatsITSMFT O2::DataFormatsTRD O2::DataFormatsTOF - O2::DataFormatsGlobalTracking) + O2::DataFormatsGlobalTracking + O2::GPUTracking) o2_target_root_dictionary(SpacePoints HEADERS include/SpacePoints/TrackResiduals.h diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/ResidualAggregator.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/ResidualAggregator.h index a02d830cfe45d..00af697da3a9b 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/ResidualAggregator.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/ResidualAggregator.h @@ -49,7 +49,7 @@ struct ResidualsContainer { void fillStatisticsBranches(); uint64_t getNEntries() const { return nResidualsTotal; } - void fill(const o2::dataformats::TFIDInfo& ti, const gsl::span resid, const gsl::span trkRefsIn, const gsl::span* trkDataIn, const o2::ctp::LumiInfo* lumiInput); + void fill(const o2::dataformats::TFIDInfo& ti, const gsl::span resid, const gsl::span detInfoRes, const gsl::span trkRefsIn, const gsl::span* trkDataIn, const o2::ctp::LumiInfo* lumiInput); void merge(ResidualsContainer* prev); void print(); void writeToFile(bool closeFileAfterwards); @@ -64,6 +64,7 @@ struct ResidualsContainer { std::vector sumUnbinnedResid, *sumUnbinnedResidPtr{&sumUnbinnedResid}; ///< sum of unbinned residuals for each TF std::vector lumi, *lumiPtr{&lumi}; ///< luminosity information from CTP per TF std::vector unbinnedRes, *unbinnedResPtr{&unbinnedRes}; ///< unbinned residuals which are sent to the aggregator + std::vector detInfoUnbRes, *detInfoUnbResPtr{&detInfoUnbRes}; ///< detector info associated to unbinned residuals which are sent to the aggregator std::vector trkData, *trkDataPtr{&trkData}; ///< track data and cluster ranges std::vector trackInfo, *trackInfoPtr{&trackInfo}; ///< allows to obtain track type for each unbinned residual downstream o2::ctp::LumiInfo lumiTF; ///< for each processed TF we store the lumi information in the tree of unbinned residuals diff --git a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h index 819ca7b0ae07f..8b884209dd697 100644 --- a/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h +++ b/Detectors/TPC/calibration/SpacePoints/include/SpacePoints/SpacePointsCalibConfParam.h @@ -41,6 +41,8 @@ struct SpacePointsCalibConfParam : public o2::conf::ConfigurableParamHelper(word & 0xFFFFu); } + uint16_t qMaxTPC() const { return static_cast((word >> 16) & 0xFFFFu); } + void setTPC(uint16_t qTot, uint16_t qMax) { word = (static_cast(qMax) << 16) | static_cast(qTot); } + // + // TRD view: q0, q1, q2 + calibrated slope (truncated to in +-3.5 range) + static constexpr uint32_t TRDQ0NB = 7, TRDQ1NB = 7, TRDQ2NB = 6, TRDSlpNB = 12; + static constexpr uint32_t TRDQ0Msk = (1 << TRDQ0NB) - 1, TRDQ1Msk = (1 << TRDQ1NB) - 1, TRDQ2Msk = ((1 << TRDQ2NB) - 1), TRDSlpMsk = (1 << TRDSlpNB) - 1; + static constexpr float TRDMaxSlope = 3.5, TRDSlope2Int = ((1 << TRDSlpNB) - 1) / (2 * TRDMaxSlope), TRDInt2Slope = 1.f / TRDSlope2Int; + uint16_t q0TRD() const { return static_cast(word & TRDQ0Msk); } + uint16_t q1TRD() const { return static_cast((word >> TRDQ0NB) & TRDQ1Msk); } + uint16_t q2TRD() const { return static_cast((word >> (TRDQ0NB + TRDQ1NB)) & TRDQ2Msk); } + float slopeTRD() const { return ((word >> (TRDQ0NB + TRDQ1NB + TRDQ2NB)) & TRDSlpMsk) * TRDInt2Slope - TRDMaxSlope; } + void setTRD(uint8_t q0, uint8_t q1, uint8_t q2, float slope) + { + float rslope = (slope + TRDMaxSlope) * TRDSlope2Int; + if (rslope < 0.f) { + rslope = 0; + } else if (rslope > TRDSlpMsk) { + rslope = TRDSlpMsk; + } + uint32_t slpI = std::round(rslope); + word = (static_cast(slpI << (TRDQ0NB + TRDQ1NB + TRDQ2NB)) | + static_cast((q2 & TRDQ2Msk) << (TRDQ0NB + TRDQ1NB)) | + static_cast((q1 & TRDQ1Msk) << TRDQ0NB) | + static_cast(q0 & TRDQ0Msk)); + } + // + // TOF view (time difference in \mus wrt seeding ITS-TPC track) + float timeTOF() const { return std::bit_cast(word); } + void setTOF(float t) { word = std::bit_cast(t); } + // + // No info for ITS is stored + // + // PV view (time difference in \mus wrt contributing ITS-TPC track) + float timePV() const { return std::bit_cast(word); } + void setPV(float t) { word = std::bit_cast(t); } + + ClassDefNV(DetInfoResid, 1); +}; + /// Structure for the information required to associate each residual with a given track type (ITS-TPC-TRD-TOF, etc) struct TrackDataCompact { TrackDataCompact() = default; - TrackDataCompact(uint32_t idx, uint8_t nRes, uint8_t source, uint8_t nextraRes = 0) : idxFirstResidual(idx), nResiduals(nRes), sourceId(source), nExtDetResid(nextraRes) {} + TrackDataCompact(uint32_t idx, std::array mlt, uint8_t nRes, uint8_t source, uint8_t nextraRes = 0) : idxFirstResidual(idx), multStack{mlt}, nResiduals(nRes), sourceId(source), nExtDetResid(nextraRes) {} uint32_t idxFirstResidual; ///< the index of the first residual from this track + std::array multStack{}; // multiplicity in the stack packed as asinh(x*0.05)/0.05 uint8_t nResiduals; ///< total number of TPC residuals associated to this track uint8_t nExtDetResid = 0; ///< number of external detectors (wrt TPC) residuals stored, on top of clIdx.getEntries uint8_t sourceId; ///< source ID obtained from the global track ID - ClassDefNV(TrackDataCompact, 2); + + void setMultStack(float v, int stack) + { + uint32_t mltPacked = std::round(std::asinh(v * 0.05) / 0.05); + multStack[stack] = mltPacked < 0xff ? mltPacked : 0xff; + } + float getMultStack(int stack) const + { + return std::sinh(multStack[stack] * 0.05) / 0.05; + } + float getMultStackPacked(int stack) const { return multStack[stack]; } + + ClassDefNV(TrackDataCompact, 3); }; // TODO add to UnbinnedResid::sec flag if cluster was used or not @@ -140,14 +202,31 @@ struct TrackData { float chi2TPC{}; ///< chi2 of TPC track float chi2ITS{}; ///< chi2 of ITS track float chi2TRD{}; ///< chi2 of TRD track + float deltaTOF{}; ///< TOFsignal - T0 - texp(PID), if T0 is available unsigned short nClsTPC{}; ///< number of attached TPC clusters unsigned short nClsITS{}; ///< number of attached ITS clusters unsigned short nTrkltsTRD{}; ///< number of attached TRD tracklets - unsigned short clAvailTOF{}; ///< whether or not track seed has a matched TOF cluster + unsigned short clAvailTOF{}; ///< whether or not track seed has a matched TOF cluster, if so, gives the resolution of the T0 in ps + short TRDTrkltSlope[6] = {}; ///< TRD tracklet slope 0x7fff / param::MaxTRDSlope uint8_t nExtDetResid = 0; ///< number of external detectors (to TPC) residuals stored, on top of clIdx.getEntries o2::dataformats::RangeReference<> clIdx{}; ///< index of first cluster residual and total number of TPC cluster residuals of this track - ClassDefNV(TrackData, 7); + std::array multStack{}; // multiplicity in the stack packed as asinh(x*0.05)/0.05 + float getT0Error() const { return float(clAvailTOF); } + bool isTOFAvail() const { return clAvailTOF != 0; } + + void setMultStack(float v, int stack) + { + uint32_t mltPacked = std::round(std::asinh(v * 0.05) / 0.05); + multStack[stack] = mltPacked < 0xff ? mltPacked : 0xff; + } + float getMultStack(int stack) const + { + return std::sinh(multStack[stack] * 0.05) / 0.05; + } + float getMultStackPacked(int stack) const { return multStack[stack]; } + + ClassDefNV(TrackData, 10); }; /// \class TrackInterpolation @@ -233,6 +312,8 @@ class TrackInterpolation /// Reset cache and output vectors void reset(); + // refit ITS track taking PID (unless already refitted) from the seed and reassign to the seed + bool refITSTrack(o2::dataformats::GlobalTrackID, int iSeed); // -------------------------------------- outlier rejection -------------------------------------------------- /// Validates the given input track and its residuals @@ -260,6 +341,8 @@ class TrackInterpolation void diffToMA(const int np, const std::array& y, std::array& diffMA) const; // -------------------------------------- settings -------------------------------------------------- + void setNHBPerTF(int n) { mNHBPerTF = n; } + void setTPCVDrift(const o2::tpc::VDriftCorrFact& v); /// Sets the flag if material correction should be applied when extrapolating the tracks @@ -288,10 +371,13 @@ class TrackInterpolation void setExtDetResid(bool v) { mExtDetResid = v; } - int processTRDLayer(const o2::trd::TrackTRD& trkTRD, int iLayer, o2::track::TrackParCov& trkWork, std::array* trkltTRDYZ = nullptr, std::array* trkltTRDCov = nullptr); + int processTRDLayer(const o2::trd::TrackTRD& trkTRD, int iLayer, o2::track::TrackParCov& trkWork, std::array* trkltTRDYZ = nullptr, + std::array* trkltTRDCov = nullptr, TrackData* trkData = nullptr, + o2::trd::Tracklet64* trk64 = nullptr, o2::trd::CalibratedTracklet* trkCalib = nullptr); // --------------------------------- output --------------------------------------------- std::vector& getClusterResiduals() { return mClRes; } + std::vector& getClusterResidualsDetInfo() { return mDetInfoRes; } std::vector& getTrackDataCompact() { return mTrackDataCompact; } std::vector& getTrackDataExtended() { return mTrackDataExtended; } std::vector& getReferenceTracks() { return mTrackData; } @@ -300,8 +386,14 @@ class TrackInterpolation private: static constexpr float sFloatEps{1.e-7f}; ///< float epsilon for robust linear fitting + static constexpr int NSTACKS = 4; + static constexpr std::array STACKROWS{0, 63, 97, 127, 152}; // parameters + settings const SpacePointsCalibConfParam* mParams = nullptr; + std::shared_ptr mTPCParam = nullptr; + int mNHBPerTF = 32; + int mNTPCOccBinLength = 16; ///< TPC occupancy bin length in TB + float mNTPCOccBinLengthInv = 1.f / 16; ///< its inverse float mTPCTimeBinMUS{.2f}; ///< TPC time bin duration in us float mTPCVDriftRef = -1.; ///< TPC nominal drift speed in cm/microseconds float mTPCDriftTimeOffsetRef = 0.; ///< TPC nominal (e.g. at the start of run) drift time bias in cm/mus @@ -322,6 +414,7 @@ class TrackInterpolation std::vector mGIDs{}; ///< GIDs of input tracks std::vector mGIDtables{}; ///< GIDs of contributors from single detectors for each seed std::vector mTrackTimes{}; ///< time estimates for all input tracks in micro seconds + std::vector mTrackPVID{}; ///< track vertex index (if any) std::vector mSeeds{}; ///< seeding track parameters (ITS tracks) std::vector mParentID{}; ///< entry of more global parent track for skimmed seeds (-1: no parent) std::map mTrackTypes; ///< mapping of track source to array index in mTrackIndices @@ -331,6 +424,7 @@ class TrackInterpolation // ITS specific input only needed for debugging gsl::span mITSTrackClusIdx; ///< input ITS track cluster indices span std::vector> mITSClustersArray; ///< ITS clusters created in run() method from compact clusters + std::vector mITSRefitSeedID; ///< seed ID first using refitted ITS track const o2::itsmft::TopologyDictionary* mITSDict = nullptr; ///< cluster patterns dictionary // output @@ -338,6 +432,7 @@ class TrackInterpolation std::vector mTrackDataCompact{}; ///< required to connect each residual to a global track std::vector mTrackDataExtended{}; ///< full tracking information for debugging std::vector mClRes{}; ///< residuals for each available TPC cluster of all tracks + std::vector mDetInfoRes{}; ///< packed detector info associated with each residual std::vector mTrackDataUnfiltered{}; ///< same as mTrackData, but for all tracks before outlier filtering std::vector mClResUnfiltered{}; ///< same as mClRes, but for all residuals before outlier filtering @@ -346,7 +441,7 @@ class TrackInterpolation std::vector mGIDsSuccess; ///< keep track of the GIDs which could be processed successfully // helpers - o2::trd::RecoParam mRecoParam; ///< parameters required for TRD refit + o2::gpu::GPUTRDRecoParam mRecoParam; ///< parameters required for TRD refit o2::trd::Geometry* mGeoTRD; ///< TRD geometry instance (needed for tilted pad correction) std::unique_ptr mFastTransform{}; ///< TPC cluster transformation float mBz; ///< required for helix approximation 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/ResidualAggregator.cxx b/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx index a120c0e4ae782..b916e14dbf741 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/ResidualAggregator.cxx @@ -124,6 +124,7 @@ void ResidualsContainer::init(const TrackResiduals* residualsEngine, std::string treeOutResidualsUnbinned->Branch("trackInfo", &trackInfoPtr); treeOutResidualsUnbinned->Branch("CTPLumi", &lumiTF); treeOutResidualsUnbinned->Branch("timeMS", &timeMS); + treeOutResidualsUnbinned->Branch("detInfo", &detInfoUnbResPtr); } if (writeTrackData) { treeOutTrackData = std::make_unique("trackData", "Track information incl cluster range ref"); @@ -170,7 +171,7 @@ void ResidualsContainer::fillStatisticsBranches() } } -void ResidualsContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::span resid, const gsl::span trkRefsIn, const gsl::span* trkDataIn, const o2::ctp::LumiInfo* lumiInput) +void ResidualsContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::span resid, const gsl::span detInfoRes, const gsl::span trkRefsIn, const gsl::span* trkDataIn, const o2::ctp::LumiInfo* lumiInput) { // receives large vector of unbinned residuals and fills the sector-wise vectors // with binned residuals and statistics @@ -185,13 +186,14 @@ void ResidualsContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::sp firstSeenTF = ti.tfCounter; } for (const auto& residIn : resid) { - ++nUnbinnedResidualsInTF; bool counterIncremented = false; if (writeUnbinnedResiduals) { unbinnedRes.push_back(residIn); + detInfoUnbRes.push_back(detInfoRes.size() ? detInfoRes[nUnbinnedResidualsInTF] : DetInfoResid{}); ++nResidualsTotal; counterIncremented = true; } + ++nUnbinnedResidualsInTF; if (!writeBinnedResid) { continue; } @@ -247,6 +249,7 @@ void ResidualsContainer::fill(const o2::dataformats::TFIDInfo& ti, const gsl::sp timeMS = orbitReset + ti.tfCounter * o2::constants::lhc::LHCOrbitMUS * 1.e-3; treeOutResidualsUnbinned->Fill(); unbinnedRes.clear(); + detInfoUnbRes.clear(); trackInfo.clear(); } tfOrbits.push_back(ti.firstTForbit); @@ -338,6 +341,9 @@ void ResidualsContainer::merge(ResidualsContainer* prev) if (writeUnbinnedResiduals) { prev->treeOutResidualsUnbinned->SetBranchAddress("res", &unbinnedResPtr); prev->treeOutResidualsUnbinned->SetBranchAddress("trackInfo", &trackInfoPtr); + prev->treeOutResidualsUnbinned->SetBranchAddress("CTPLumi", &lumiTF); + prev->treeOutResidualsUnbinned->SetBranchAddress("timeMS", &timeMS); + prev->treeOutResidualsUnbinned->SetBranchAddress("detInfo", &detInfoUnbResPtr); for (int i = 0; i < treeOutResidualsUnbinned->GetEntries(); ++i) { treeOutResidualsUnbinned->GetEntry(i); prev->treeOutResidualsUnbinned->Fill(); diff --git a/Detectors/TPC/calibration/SpacePoints/src/SpacePointCalibLinkDef.h b/Detectors/TPC/calibration/SpacePoints/src/SpacePointCalibLinkDef.h index b109a610f60b5..a3f9f3fe2267c 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/SpacePointCalibLinkDef.h +++ b/Detectors/TPC/calibration/SpacePoints/src/SpacePointCalibLinkDef.h @@ -29,7 +29,9 @@ #pragma link C++ class o2::tpc::TrackResiduals::VoxRes + ; #pragma link C++ class o2::tpc::TrackResiduals::VoxStats + ; #pragma link C++ class o2::tpc::UnbinnedResid + ; +#pragma link C++ class o2::tpc::DetInfoResid + ; #pragma link C++ class std::vector < o2::tpc::UnbinnedResid> + ; +#pragma link C++ class std::vector < o2::tpc::DetInfoResid> + ; #pragma link C++ class std::vector < o2::tpc::TrackResiduals::LocalResid> + ; #pragma link C++ class std::vector < o2::tpc::TrackResiduals::VoxStats> + ; #pragma link C++ class o2::tpc::ResidualAggregator + ; diff --git a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx index 1daaa897e9756..76daab93dd8e0 100644 --- a/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx +++ b/Detectors/TPC/calibration/SpacePoints/src/TrackInterpolation.cxx @@ -33,6 +33,11 @@ #include "DataFormatsTPC/VDriftCorrFact.h" #include "Framework/Logger.h" #include "CCDB/BasicCCDBManager.h" +#include "GPUO2InterfaceUtils.h" +#include "GPUO2InterfaceConfiguration.h" +#include "GPUO2InterfaceRefit.h" +#include "GPUParam.h" +#include "GPUParam.inc" #include #include #include @@ -121,7 +126,7 @@ void TrackInterpolation::init(o2::dataformats::GlobalTrackID::mask_t src, o2::da mFastTransform = std::move(TPCFastTransformHelperO2::instance()->create(0)); mBz = o2::base::Propagator::Instance()->getNominalBz(); - mRecoParam.setBfield(mBz); + mRecoParam.init(mBz); mGeoTRD = o2::trd::Geometry::instance(); mParams = &SpacePointsCalibConfParam::Instance(); @@ -135,7 +140,7 @@ void TrackInterpolation::init(o2::dataformats::GlobalTrackID::mask_t src, o2::da auto geom = o2::its::GeometryTGeo::Instance(); geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); - + mTPCParam = o2::gpu::GPUO2InterfaceUtils::getFullParamShared(0.f, mNHBPerTF); mInitDone = true; LOGP(info, "Done initializing TrackInterpolation. Configured track input: {}. Track input specifically for map: {}", GTrackID::getSourcesNames(mSourcesConfigured), mSingleSourcesConfigured ? "identical" : GTrackID::getSourcesNames(mSourcesConfiguredMap)); @@ -221,6 +226,9 @@ void TrackInterpolation::prepareInputTrackSample(const o2::globaltracking::RecoC int nv = vtxRefs.size() - 1; GTrackID::mask_t allowedSources = GTrackID::getSourcesMask("ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF") & mSourcesConfigured; constexpr std::array SrcFast = {int(GTrackID::ITSTPCTRD), int(GTrackID::ITSTPCTOF), int(GTrackID::ITSTPCTRDTOF)}; + if (mParams->refitITS) { + mITSRefitSeedID.resize(mRecoCont->getITSTracks().size(), -1); + } for (int iv = 0; iv < nv; iv++) { LOGP(debug, "processing PV {} of {}", iv, nv); @@ -281,6 +289,7 @@ void TrackInterpolation::prepareInputTrackSample(const o2::globaltracking::RecoC mGIDtables.push_back(gidTable); mTrackTimes.push_back(pv.getTimeStamp().getTimeStamp()); mTrackIndices[mTrackTypes[vid.getSource()]].push_back(nTrackSeeds++); + mTrackPVID.push_back(iv); } } } @@ -312,6 +321,10 @@ void TrackInterpolation::process() // set the input containers mTPCTracksClusIdx = mRecoCont->getTPCTracksClusterRefs(); mTPCClusterIdxStruct = &mRecoCont->getTPCClusters(); + int nbOccTOT = o2::gpu::GPUO2InterfaceRefit::fillOccupancyMapGetSize(mNHBPerTF, mTPCParam.get()); + o2::gpu::GPUO2InterfaceUtils::paramUseExternalOccupancyMap(mTPCParam.get(), mNHBPerTF, mRecoCont->occupancyMapTPC.data(), nbOccTOT); + mNTPCOccBinLength = mTPCParam->rec.tpc.occupancyMapTimeBins; + mNTPCOccBinLengthInv = 1.f / mNTPCOccBinLength; { if (!mITSDict) { LOG(error) << "No ITS dictionary available"; @@ -350,6 +363,7 @@ void TrackInterpolation::process() int maxOutputTracks = (mMaxTracksPerTF >= 0) ? mMaxTracksPerTF + mAddTracksForMapPerTF : nSeeds; mTrackData.reserve(maxOutputTracks); mClRes.reserve(maxOutputTracks * param::NPadRows); + mDetInfoRes.reserve(maxOutputTracks * param::NPadRows); bool maxTracksReached = false; for (int iSeed = 0; iSeed < nSeeds; ++iSeed) { if (mMaxTracksPerTF >= 0 && mTrackDataCompact.size() >= mMaxTracksPerTF + mAddTracksForMapPerTF) { @@ -360,13 +374,13 @@ void TrackInterpolation::process() if (mParams->enableTrackDownsampling && !isTrackSelected(mSeeds[seedIndex])) { continue; } - auto addPart = [this, seedIndex](GTrackID::Source src) { this->mGIDs.push_back(this->mGIDtables[seedIndex][src]); this->mGIDtables.push_back(this->mRecoCont->getSingleDetectorRefs(this->mGIDs.back())); this->mTrackTimes.push_back(this->mTrackTimes[seedIndex]); this->mSeeds.push_back(this->mSeeds[seedIndex]); this->mParentID.push_back(seedIndex); // store parent seed id + this->mTrackPVID.push_back(this->mTrackPVID[seedIndex]); }; GTrackID::mask_t partsAdded; @@ -431,6 +445,8 @@ void TrackInterpolation::interpolateTrack(int iSeed) { LOGP(debug, "Starting track interpolation for GID {}", mGIDs[iSeed].asString()); TrackData trackData; + o2::trd::Tracklet64 trkl64; + o2::trd::CalibratedTracklet trklCalib; std::unique_ptr trackDataExtended; std::vector clusterResiduals; auto propagator = o2::base::Propagator::Instance(); @@ -450,9 +466,12 @@ void TrackInterpolation::interpolateTrack(int iSeed) (*trackDataExtended).clsITS.push_back(clsITS); } } + if (mParams->refitITS && !refITSTrack(gidTable[GTrackID::ITS], iSeed)) { + return; + } trackData.gid = mGIDs[iSeed]; trackData.par = mSeeds[iSeed]; - auto& trkWork = mSeeds[iSeed]; + auto trkWork = mSeeds[iSeed]; o2::track::TrackPar trkInner{trkWork}; // reset the cache array (sufficient to set cluster available to zero) for (auto& elem : mCache) { @@ -461,7 +480,9 @@ void TrackInterpolation::interpolateTrack(int iSeed) trackData.clIdx.setFirstEntry(mClRes.size()); // reference the first cluster residual belonging to this track float clusterTimeBinOffset = mTrackTimes[iSeed] / mTPCTimeBinMUS; - // store the TPC cluster positions in the cache + // store the TPC cluster positions in the cache, as well as dedx info + std::array, constants::MAXGLOBALPADROW> mCacheDEDX{}; + std::array multBins{}; for (int iCl = trkTPC.getNClusterReferences(); iCl--;) { uint8_t sector, row; uint32_t clusterIndexInRow; @@ -474,6 +495,12 @@ void TrackInterpolation::interpolateTrack(int iSeed) mCache[row].clY = clTPCYZ[0]; mCache[row].clZ = clTPCYZ[1]; mCache[row].clAngle = o2::math_utils::sector2Angle(sector); + mCacheDEDX[row].first = clTPC.getQtot(); + mCacheDEDX[row].second = clTPC.getQmax(); + int imb = int(clTPC.getTime() * mNTPCOccBinLengthInv); + if (imb < mTPCParam->occupancyMapSize) { + multBins[row] = 1 + std::max(0, imb); + } } // extrapolate seed through TPC and store track position at each pad row @@ -620,7 +647,22 @@ void TrackInterpolation::interpolateTrack(int iSeed) trackData.nClsTPC = trkTPC.getNClusterReferences(); trackData.nClsITS = trkITS.getNumberOfClusters(); trackData.nTrkltsTRD = gidTable[GTrackID::TRD].isIndexSet() ? mRecoCont->getITSTPCTRDTrack(gidTable[GTrackID::ITSTPCTRD]).getNtracklets() : 0; - trackData.clAvailTOF = gidTable[GTrackID::TOF].isIndexSet() ? 1 : 0; + + double t0forTOF = 0.; // to be set if TOF is matched + float t0forTOFwithinBC = 0.f; + float t0forTOFres = 9999.f; + + if (gidTable[GTrackID::TOF].isIndexSet()) { + const auto& tofMatch = mRecoCont->getTOFMatch(mGIDs[iSeed]); + ULong64_t bclongtof = (tofMatch.getSignal() - 10000) * o2::tof::Geo::BC_TIME_INPS_INV; + t0forTOF = tofMatch.getFT0Best(); // setting t0 for TOF + t0forTOFwithinBC = t0forTOF - bclongtof * o2::tof::Geo::BC_TIME_INPS; + t0forTOFres = tofMatch.getFT0BestRes(); + trackData.deltaTOF = tofMatch.getSignal() - t0forTOF - tofMatch.getLTIntegralOut().getTOF(trkTPC.getPID().getID()); + trackData.clAvailTOF = uint16_t(t0forTOFres); + } else { + trackData.clAvailTOF = 0; + } trackData.dEdxTPC = trkTPC.getdEdx().dEdxTotTPC; TrackParams params; // for refitted track parameters and flagging rejected clusters @@ -634,7 +676,6 @@ void TrackInterpolation::interpolateTrack(int iSeed) // skip masked cluster residual continue; } - ++nClValidated; const float tgPhi = clusterResiduals[iCl].snp / std::sqrt((1.f - clusterResiduals[iCl].snp) * (1.f + clusterResiduals[iCl].snp)); const auto dy = clusterResiduals[iCl].dy; const auto dz = clusterResiduals[iCl].dz; @@ -643,12 +684,38 @@ void TrackInterpolation::interpolateTrack(int iSeed) const auto sec = clusterResiduals[iCl].sec; if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, y, z, iRow, sec); + mDetInfoRes.emplace_back().setTPC(mCacheDEDX[iRow].first, mCacheDEDX[iRow].second); // qtot, qmax + ++nClValidated; } else { ++mRejectedResiduals; } } trackData.clIdx.setEntries(nClValidated); + // store multiplicity info + for (int ist = 0; ist < NSTACKS; ist++) { + int mltBinMin = 0x7ffff, mltBinMax = -1, prevBin = -1; + for (int ir = STACKROWS[ist]; ir < STACKROWS[ist + 1]; ir++) { + if (multBins[ir] != prevBin && multBins[ir] > 0) { // there is a cluster different from previous one + prevBin = multBins[ir]; + if (multBins[ir] > mltBinMax) { + mltBinMax = multBins[ir]; + } + if (multBins[ir] < mltBinMin) { + mltBinMin = multBins[ir]; + } + } + } + if (--mltBinMin >= 0) { // we were offsetting bin IDs by 1! + float avMlt = 0; + for (int ib = mltBinMin; ib < mltBinMax; ib++) { + avMlt += mTPCParam->occupancyMap[ib]; + } + avMlt /= (mltBinMax - mltBinMin); + trackData.setMultStack(avMlt, ist); + } + } + bool stopPropagation = !mExtDetResid; if (!stopPropagation) { // do we have TRD residuals to add? @@ -657,7 +724,7 @@ void TrackInterpolation::interpolateTrack(int iSeed) const auto& trkTRD = mRecoCont->getITSTPCTRDTrack(gidTable[GTrackID::ITSTPCTRD]); for (int iLayer = 0; iLayer < o2::trd::constants::NLAYER; iLayer++) { std::array trkltTRDYZ{}; - int res = processTRDLayer(trkTRD, iLayer, trkWork, &trkltTRDYZ); + int res = processTRDLayer(trkTRD, iLayer, trkWork, &trkltTRDYZ, nullptr, &trackData, &trkl64, &trklCalib); if (res == -1) { // no traklet on this layer continue; } @@ -671,6 +738,7 @@ void TrackInterpolation::interpolateTrack(int iSeed) auto dz = trkltTRDYZ[1] - trkWork.getZ(); if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWork.getY()) < param::MaxY) && (std::abs(trkWork.getZ()) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWork.getY(), trkWork.getZ(), 160 + iLayer, o2::math_utils::angle2Sector(trkWork.getAlpha()), (short)res); + mDetInfoRes.emplace_back().setTRD(trkl64.getQ0(), trkl64.getQ1(), trkl64.getQ2(), trklCalib.getDy()); // q0,q1,q2,slope trackData.nExtDetResid++; } } @@ -697,8 +765,16 @@ void TrackInterpolation::interpolateTrack(int iSeed) float tgPhi = trkWork.getSnp() / std::sqrt((1.f - trkWork.getSnp()) * (1.f + trkWork.getSnp())); auto dy = clTOFxyz[1] - trkWork.getY(); auto dz = clTOFxyz[2] - trkWork.getZ(); + // get seeding track time + if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWork.getY()) < param::MaxY) && (std::abs(trkWork.getZ()) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWork.getY(), trkWork.getZ(), 170, clTOF.getCount(), clTOF.getPadInSector()); + // get seeding track time + if (!gidTable[GTrackID::ITSTPC].isIndexSet()) { + LOGP(fatal, "ITS-TPC seed index is not set for TOF track"); + } + float tdif = static_cast(clTOF.getTime() - t0forTOF); // time in \mus wrt interaction time0 + mDetInfoRes.emplace_back().setTOF(tdif * 1e-6); trackData.nExtDetResid++; } break; @@ -725,6 +801,33 @@ void TrackInterpolation::interpolateTrack(int iSeed) auto dz = cls.getZ() - 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) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWorkITS.getY(), trkWorkITS.getZ(), 180 + geom->getLayer(cls.getSensorID()), -1, cls.getSensorID()); + mDetInfoRes.emplace_back(); // empty placeholder + trackData.nExtDetResid++; + } + } + if (!stopPropagation) { // add residual to PV + const auto& pv = mRecoCont->getPrimaryVertices()[mTrackPVID[iSeed]]; + o2::math_utils::Point3D vtx{pv.getX(), pv.getY(), pv.getZ()}; + if (!propagator->propagateToDCA(vtx, trkWorkITS, mBz, mParams->maxStep, mMatCorr)) { + LOGP(debug, "Failed propagation to DCA to PV ({} {} {}), {}", pv.getX(), pv.getY(), pv.getZ(), trkWorkITS.asString()); + stopPropagation = true; + break; + } + // rotate PV to the track frame + float sn, cs, alpha = trkWorkITS.getAlpha(); + math_utils::detail::bringToPMPi(alpha); + math_utils::detail::sincos(alpha, sn, cs); + 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) && 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()) { + LOGP(fatal, "ITS-TPC seed index is not set for TOF track"); + } + float tdif = pv.getTimeStamp().getTimeStamp() - mRecoCont->getTPCITSTrack(gidTable[GTrackID::ITSTPC]).getTimeMUS().getTimeStamp(); + mDetInfoRes.emplace_back().setPV(tdif); // time in \mus wrt seeding ITS-TPC track trackData.nExtDetResid++; } } @@ -733,7 +836,7 @@ void TrackInterpolation::interpolateTrack(int iSeed) } mGIDsSuccess.push_back(mGIDs[iSeed]); - mTrackDataCompact.emplace_back(trackData.clIdx.getFirstEntry(), nClValidated, mGIDs[iSeed].getSource(), trackData.nExtDetResid); + mTrackDataCompact.emplace_back(trackData.clIdx.getFirstEntry(), trackData.multStack, nClValidated, mGIDs[iSeed].getSource(), trackData.nExtDetResid); mTrackData.push_back(std::move(trackData)); if (mDumpTrackPoints) { (*trackDataExtended).clIdx.setEntries(nClValidated); @@ -751,7 +854,8 @@ void TrackInterpolation::interpolateTrack(int iSeed) } int TrackInterpolation::processTRDLayer(const o2::trd::TrackTRD& trkTRD, int iLayer, o2::track::TrackParCov& trkWork, - std::array* trkltTRDYZ, std::array* trkltTRDCov) + std::array* trkltTRDYZ, std::array* trkltTRDCov, TrackData* trkData, + o2::trd::Tracklet64* trk64, o2::trd::CalibratedTracklet* trkCalib) { // return chamber ID (0:539) in case of successful processing, -1 if there is no TRD tracklet at given layer, -2 if processing failed int trkltIdx = trkTRD.getTrackletIndex(iLayer); @@ -787,6 +891,18 @@ int TrackInterpolation::processTRDLayer(const o2::trd::TrackTRD& trkTRD, int iLa mRecoParam.recalcTrkltCov(tilt, trkWork.getSnp(), pad->getRowSize(trdTrklt.getPadRow()), *trkltTRDCov); } } + if (trkData) { + auto slope = trdSP.getDy(); + if (std::abs(slope) < param::MaxTRDSlope) { + trkData->TRDTrkltSlope[iLayer] = slope * 0x7fff / param::MaxTRDSlope; + } + } + if (trk64) { + *trk64 = trdTrklt; + } + if (trkCalib) { + *trkCalib = trdSP; + } return trkltDet; } @@ -796,6 +912,8 @@ void TrackInterpolation::extrapolateTrack(int iSeed) LOGP(debug, "Starting track extrapolation for GID {}", mGIDs[iSeed].asString()); const auto& gidTable = mGIDtables[iSeed]; TrackData trackData; + o2::trd::Tracklet64 trkl64; + o2::trd::CalibratedTracklet trklCalib; std::unique_ptr trackDataExtended; std::vector clusterResiduals; trackData.clIdx.setFirstEntry(mClRes.size()); @@ -814,6 +932,9 @@ void TrackInterpolation::extrapolateTrack(int iSeed) (*trackDataExtended).clsITS.push_back(clsITS); } } + if (mParams->refitITS && !refITSTrack(gidTable[GTrackID::ITS], iSeed)) { + return; + } trackData.gid = mGIDs[iSeed]; trackData.par = mSeeds[iSeed]; @@ -823,6 +944,8 @@ void TrackInterpolation::extrapolateTrack(int iSeed) unsigned short rowPrev = 0; // used to calculate dRow of two consecutive cluster residuals unsigned short nMeasurements = 0; uint8_t clRowPrev = constants::MAXGLOBALPADROW; // used to identify and skip split clusters on the same pad row + std::array, constants::MAXGLOBALPADROW> mCacheDEDX{}; + std::array multBins{}; for (int iCl = trkTPC.getNClusterReferences(); iCl--;) { uint8_t sector, row; uint32_t clusterIndexInRow; @@ -854,16 +977,20 @@ void TrackInterpolation::extrapolateTrack(int iSeed) const auto tz = trkWork.getZ(); const auto snp = trkWork.getSnp(); const auto sec = sector; - clusterResiduals.emplace_back(dY, dZ, ty, tz, snp, sec, row - rowPrev); - + mCacheDEDX[row].first = cl.getQtot(); + mCacheDEDX[row].second = cl.getQmax(); rowPrev = row; + int imb = int(cl.getTime() * mNTPCOccBinLengthInv); + if (imb < mTPCParam->occupancyMapSize) { + multBins[row] = 1 + std::max(0, imb); + } ++nMeasurements; } TrackParams params; // for refitted track parameters and flagging rejected clusters if (clusterResiduals.size() > constants::MAXGLOBALPADROW) { - LOGP(warn, "Extrapolated ITS-TPC track and found more reesiduals than possible ({})", clusterResiduals.size()); + LOGP(warn, "Extrapolated ITS-TPC track and found more residuals than possible ({})", clusterResiduals.size()); return; } @@ -887,7 +1014,6 @@ void TrackInterpolation::extrapolateTrack(int iSeed) if (iRow < param::NPadRows && params.flagRej[iCl]) { // skip masked cluster residual continue; } - ++nClValidated; const float tgPhi = clusterResiduals[iCl].snp / std::sqrt((1.f - clusterResiduals[iCl].snp) * (1.f + clusterResiduals[iCl].snp)); const auto dy = clusterResiduals[iCl].dy; const auto dz = clusterResiduals[iCl].dz; @@ -895,12 +1021,38 @@ void TrackInterpolation::extrapolateTrack(int iSeed) const auto z = clusterResiduals[iCl].z; if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(y) < param::MaxY) && (std::abs(z) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, y, z, iRow, clusterResiduals[iCl].sec); + mDetInfoRes.emplace_back().setTPC(mCacheDEDX[iRow].first, mCacheDEDX[iRow].second); // qtot, qmax + ++nClValidated; } else { ++mRejectedResiduals; } } trackData.clIdx.setEntries(nClValidated); + // store multiplicity info + for (int ist = 0; ist < NSTACKS; ist++) { + int mltBinMin = 0x7ffff, mltBinMax = -1, prevBin = -1; + for (int ir = STACKROWS[ist]; ir < STACKROWS[ist + 1]; ir++) { + if (multBins[ir] != prevBin && multBins[ir] > 0) { // there is a cluster + prevBin = multBins[ir]; + if (multBins[ir] > mltBinMax) { + mltBinMax = multBins[ir]; + } + if (multBins[ir] < mltBinMin) { + mltBinMin = multBins[ir]; + } + } + } + if (--mltBinMin >= 0) { // we were offsetting bin IDs by 1! + float avMlt = 0; + for (int ib = mltBinMin; ib < mltBinMax; ib++) { + avMlt += mTPCParam->occupancyMap[ib]; + } + avMlt /= (mltBinMax - mltBinMin); + trackData.setMultStack(avMlt, ist); + } + } + bool stopPropagation = !mExtDetResid; if (!stopPropagation) { // do we have TRD residuals to add? @@ -909,9 +1061,10 @@ void TrackInterpolation::extrapolateTrack(int iSeed) const auto& gidTableFull = mGIDtables[iSeedFull]; if (gidTableFull[GTrackID::TRD].isIndexSet()) { const auto& trkTRD = mRecoCont->getITSTPCTRDTrack(gidTableFull[GTrackID::ITSTPCTRD]); + trackData.nTrkltsTRD = trkTRD.getNtracklets(); for (int iLayer = 0; iLayer < o2::trd::constants::NLAYER; iLayer++) { std::array trkltTRDYZ{}; - int res = processTRDLayer(trkTRD, iLayer, trkWork, &trkltTRDYZ); + int res = processTRDLayer(trkTRD, iLayer, trkWork, &trkltTRDYZ, nullptr, &trackData, &trkl64, &trklCalib); if (res == -1) { // no traklet on this layer continue; } @@ -926,14 +1079,22 @@ void TrackInterpolation::extrapolateTrack(int iSeed) const auto sec = clusterResiduals[iCl].sec; if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWork.getY()) < param::MaxY) && (std::abs(trkWork.getZ()) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWork.getY(), trkWork.getZ(), 160 + iLayer, o2::math_utils::angle2Sector(trkWork.getAlpha()), (short)res); - trackData.nTrkltsTRD++; + mDetInfoRes.emplace_back().setTRD(trkl64.getQ0(), trkl64.getQ1(), trkl64.getQ2(), trklCalib.getDy()); // q0,q1,q2,slope trackData.nExtDetResid++; } } } // do we have TOF residual to add? + trackData.clAvailTOF = 0; while (gidTableFull[GTrackID::TOF].isIndexSet() && !stopPropagation) { + const auto& tofMatch = mRecoCont->getTOFMatch(gidFull); + ULong64_t bclongtof = (tofMatch.getSignal() - 10000) * o2::tof::Geo::BC_TIME_INPS_INV; + double t0forTOF = tofMatch.getFT0Best(); // setting t0 for TOF + float t0forTOFwithinBC = t0forTOF - bclongtof * o2::tof::Geo::BC_TIME_INPS; + float t0forTOFres = tofMatch.getFT0BestRes(); + trackData.deltaTOF = tofMatch.getSignal() - t0forTOF - tofMatch.getLTIntegralOut().getTOF(trkTPC.getPID().getID()); + trackData.clAvailTOF = uint16_t(t0forTOFres); const auto& clTOF = mRecoCont->getTOFClusters()[gidTableFull[GTrackID::TOF]]; const float clTOFAlpha = o2::math_utils::sector2Angle(clTOF.getCount()); float clTOFxyz[3] = {clTOF.getX(), clTOF.getY(), clTOF.getZ()}; @@ -955,7 +1116,13 @@ void TrackInterpolation::extrapolateTrack(int iSeed) auto dz = clTOFxyz[2] - trkWork.getZ(); if ((std::abs(dy) < param::MaxResid) && (std::abs(dz) < param::MaxResid) && (std::abs(trkWork.getY()) < param::MaxY) && (std::abs(trkWork.getZ()) < param::MaxZ) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWork.getY(), trkWork.getZ(), 170, clTOF.getCount(), clTOF.getPadInSector()); - trackData.clAvailTOF = 1; + // get seeding track time + if (!gidTableFull[GTrackID::ITSTPC].isIndexSet()) { + LOGP(fatal, "ITS-TPC seed index is not set for TOF track"); + } + + float tdif = static_cast(clTOF.getTime() - t0forTOF); // time in \mus wrt interaction time0 + mDetInfoRes.emplace_back().setTOF(tdif * 1e-6); // time in \mus wrt seeding ITS-TPC track trackData.nExtDetResid++; } break; @@ -972,7 +1139,7 @@ void TrackInterpolation::extrapolateTrack(int iSeed) int chip = cls.getSensorID(); float chipX, chipAlpha; geom->getSensorXAlphaRefPlane(cls.getSensorID(), chipX, chipAlpha); - if (!trkWorkITS.rotate(chipAlpha) || !propagator->PropagateToXBxByBz(trkWorkITS, chipX, mParams->maxSnp, mParams->maxStep, mMatCorr)) { + if (!trkWorkITS.rotate(chipAlpha) || !propagator->propagateToX(trkWorkITS, chipX, mBz, mParams->maxSnp, mParams->maxStep, mMatCorr)) { LOGP(debug, "Failed final propagation to ITS X={} alpha={}", chipX, chipAlpha); stopPropagation = true; break; @@ -982,6 +1149,33 @@ void TrackInterpolation::extrapolateTrack(int iSeed) auto dz = cls.getZ() - 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) && (std::abs(tgPhi) < param::MaxTgSlp)) { mClRes.emplace_back(dy, dz, tgPhi, trkWorkITS.getY(), trkWorkITS.getZ(), 180 + geom->getLayer(cls.getSensorID()), -1, cls.getSensorID()); + mDetInfoRes.emplace_back(); // empty placeholder + trackData.nExtDetResid++; + } + } + if (!stopPropagation) { // add residual to PV + const auto& pv = mRecoCont->getPrimaryVertices()[mTrackPVID[iSeed]]; + o2::math_utils::Point3D vtx{pv.getX(), pv.getY(), pv.getZ()}; + if (!propagator->propagateToDCA(vtx, trkWorkITS, mBz, mParams->maxStep, mMatCorr)) { + LOGP(debug, "Failed propagation to DCA to PV ({} {} {}), {}", pv.getX(), pv.getY(), pv.getZ(), trkWorkITS.asString()); + stopPropagation = true; + break; + } + // rotate PV to the track frame + float sn, cs, alpha = trkWorkITS.getAlpha(); + math_utils::detail::bringToPMPi(alpha); + math_utils::detail::sincos(alpha, sn, cs); + 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) && 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()) { + LOGP(fatal, "ITS-TPC seed index is not set for TOF track"); + } + float tdif = pv.getTimeStamp().getTimeStamp() - mRecoCont->getTPCITSTrack(gidTableFull[GTrackID::ITSTPC]).getTimeMUS().getTimeStamp(); + mDetInfoRes.emplace_back().setPV(tdif); // time in \mus wrt seeding ITS-TPC track trackData.nExtDetResid++; } } @@ -990,7 +1184,7 @@ void TrackInterpolation::extrapolateTrack(int iSeed) } mTrackData.push_back(std::move(trackData)); mGIDsSuccess.push_back(mGIDs[iSeed]); - mTrackDataCompact.emplace_back(trackData.clIdx.getFirstEntry(), nClValidated, mGIDs[iSeed].getSource(), trackData.nExtDetResid); + mTrackDataCompact.emplace_back(trackData.clIdx.getFirstEntry(), trackData.multStack, nClValidated, mGIDs[iSeed].getSource(), trackData.nExtDetResid); if (mDumpTrackPoints) { (*trackDataExtended).clIdx.setEntries(nClValidated); (*trackDataExtended).nExtDetResid = trackData.nExtDetResid; @@ -1378,6 +1572,7 @@ void TrackInterpolation::reset() mTrackDataCompact.clear(); mTrackDataExtended.clear(); mClRes.clear(); + mDetInfoRes.clear(); mTrackDataUnfiltered.clear(); mClResUnfiltered.clear(); mGIDsSuccess.clear(); @@ -1388,6 +1583,8 @@ void TrackInterpolation::reset() mGIDtables.clear(); mTrackTimes.clear(); mSeeds.clear(); + mITSRefitSeedID.clear(); + mTrackPVID.clear(); } //______________________________________________ @@ -1401,3 +1598,56 @@ void TrackInterpolation::setTPCVDrift(const o2::tpc::VDriftCorrFact& v) o2::tpc::TPCFastTransformHelperO2::instance()->updateCalibration(*mFastTransform, 0, 1.0, mTPCVDriftRef, mTPCDriftTimeOffsetRef); } } + +//______________________________________________ +bool TrackInterpolation::refITSTrack(o2::dataformats::GlobalTrackID gid, int seedID) +{ + // refit ITS track outwards taking PID (unless already refitted) from the seed and reassign to the seed + auto& seed = mSeeds[seedID]; + int refitID = mITSRefitSeedID[gid.getIndex()]; + if (refitID >= 0) { // track was already refitted + if (mSeeds[refitID].getPID() == seed.getPID()) { + seed = mSeeds[refitID]; + } + return true; + } + const auto& trkITS = mRecoCont->getITSTrack(gid); + // fetch clusters + auto nCl = trkITS.getNumberOfClusters(); + auto clEntry = trkITS.getFirstClusterEntry(); + o2::track::TrackParCov track(trkITS); // start from the inner param + track.resetCovariance(); + track.setCov(track.getQ2Pt() * track.getQ2Pt() * track.getCov()[o2::track::CovLabels::kSigQ2Pt2], o2::track::CovLabels::kSigQ2Pt2); + track.setPID(seed.getPID()); + o2::track::TrackPar refLin(track); // and use it also as linearization reference + auto geom = o2::its::GeometryTGeo::Instance(); + auto prop = o2::base::Propagator::Instance(); + for (int iCl = nCl - 1; iCl >= 0; iCl--) { // clusters are stored from outer to inner layers + const auto& cls = mITSClustersArray[mITSTrackClusIdx[clEntry + iCl]]; + int chip = cls.getSensorID(); + float chipX, chipAlpha; + geom->getSensorXAlphaRefPlane(cls.getSensorID(), chipX, chipAlpha); + if (!track.rotate(chipAlpha, refLin, mBz)) { + LOGP(debug, "failed to rotate ITS tracks to alpha={} for the refit: {}", chipAlpha, track.asString()); + return false; + } + if (!prop->propagateToX(track, refLin, cls.getX(), mBz, o2::base::PropagatorImpl::MAX_SIN_PHI, o2::base::PropagatorImpl::MAX_STEP, o2::base::PropagatorF::MatCorrType::USEMatCorrLUT)) { + LOGP(debug, "failed to propagate ITS tracks to X={}: {}", cls.getX(), track.asString()); + return false; + } + std::array posTF{cls.getY(), cls.getZ()}; + std::array covTF{cls.getSigmaY2(), cls.getSigmaYZ(), cls.getSigmaZ2()}; + if (!track.update(posTF, covTF)) { + LOGP(debug, "failed to update ITS tracks by cluster ({},{})/({},{},{})", track.asString(), cls.getY(), cls.getZ(), cls.getSigmaY2(), cls.getSigmaYZ(), cls.getSigmaZ2()); + return false; + } + if (mParams->shiftRefToCluster) { + refLin.setY(posTF[0]); + refLin.setZ(posTF[1]); + } + } + seed = track; + // memorize that this ITS track was already refitted + mITSRefitSeedID[gid.getIndex()] = seedID; + return true; +} 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,25 +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 needTPCScalersWorkflow() const - { - return lumiType == 2 || enableMShapeCorrection; - } -}; - class CorrectionMapsLoader : public o2::gpu::CorrectionMapsHelper { public: @@ -56,28 +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 - float getMapMeanRate(const o2::gpu::TPCFastTransform* mp, bool lumiOverridden) const; + 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; - 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}; -#endif + 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 }; } // 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/IDCCCDBHelper.h b/Detectors/TPC/calibration/include/TPCCalibration/IDCCCDBHelper.h index 1b8ba21774f57..744201205de76 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/IDCCCDBHelper.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/IDCCCDBHelper.h @@ -17,6 +17,7 @@ #define ALICEO2_TPC_IDCCCDBHELPER_H_ #include #include "DataFormatsTPC/Defs.h" +#include "TPCBaseRecSim/PadFlags.h" #include "TPCBase/Sector.h" #include "Rtypes.h" diff --git a/Detectors/TPC/calibration/include/TPCCalibration/IDCFactorization.h b/Detectors/TPC/calibration/include/TPCCalibration/IDCFactorization.h index 1fe6486722d95..510b6c44d613b 100644 --- a/Detectors/TPC/calibration/include/TPCCalibration/IDCFactorization.h +++ b/Detectors/TPC/calibration/include/TPCCalibration/IDCFactorization.h @@ -24,6 +24,7 @@ #include "TPCCalibration/IDCContainer.h" #include "TPCCalibration/IDCGroupHelperSector.h" #include "DataFormatsTPC/Defs.h" +#include "TPCBaseRecSim/PadFlags.h" #include namespace o2::tpc diff --git a/Detectors/TPC/calibration/include/TPCCalibration/NeuralNetworkClusterizer.h b/Detectors/TPC/calibration/include/TPCCalibration/NeuralNetworkClusterizer.h deleted file mode 100644 index 196bba644714c..0000000000000 --- a/Detectors/TPC/calibration/include/TPCCalibration/NeuralNetworkClusterizer.h +++ /dev/null @@ -1,38 +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 NeuralNetworkClusterizer.h -/// \brief Fetching neural networks for clusterization from CCDB -/// \author Christian Sonnabend - -#ifndef AliceO2_TPC_NeuralNetworkClusterizer_h -#define AliceO2_TPC_NeuralNetworkClusterizer_h - -#include "CCDB/CcdbApi.h" - -namespace o2::tpc -{ - -class NeuralNetworkClusterizer -{ - public: - NeuralNetworkClusterizer() = default; - void initCcdbApi(std::string url); - void loadIndividualFromCCDB(std::map settings); - - private: - o2::ccdb::CcdbApi ccdbApi; - std::map metadata; - std::map headers; -}; - -} // namespace o2::tpc -#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/comparePedestalsAndNoise.C b/Detectors/TPC/calibration/macro/comparePedestalsAndNoise.C index 5f998453d9515..04ba2fdeafc27 100644 --- a/Detectors/TPC/calibration/macro/comparePedestalsAndNoise.C +++ b/Detectors/TPC/calibration/macro/comparePedestalsAndNoise.C @@ -12,11 +12,11 @@ #if !defined(__CLING__) || defined(__ROOTCLING__) #include "TROOT.h" #include "TFile.h" -#include "TPCBase/CalDet.h" +#include "TPCBaseRecSim/CalDet.h" #include "TH1F.h" #include "TH2F.h" #include "TCanvas.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #endif std::tuple getNoiseAndPedestalHistogram(const TString pedestalFile, int roc) 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/macro/drawNoiseAndPedestal.C b/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C index b4894ecf60eb9..45677ac7404ec 100644 --- a/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C +++ b/Detectors/TPC/calibration/macro/drawNoiseAndPedestal.C @@ -19,9 +19,9 @@ #include "TH2.h" #include "TFile.h" #include "TPCBase/CalDet.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCBase/Utils.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPad.h" #include "TCanvas.h" #include "TH1F.h" diff --git a/Detectors/TPC/calibration/macro/drawPulser.C b/Detectors/TPC/calibration/macro/drawPulser.C index 97d14cfd95a58..3be3a958b0025 100644 --- a/Detectors/TPC/calibration/macro/drawPulser.C +++ b/Detectors/TPC/calibration/macro/drawPulser.C @@ -16,7 +16,7 @@ #include "TH2.h" #include "TFile.h" #include "TPCBase/CalDet.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCBase/Utils.h" #include "TPCBase/Mapper.h" #include "TPad.h" diff --git a/Detectors/TPC/calibration/macro/prepareCMFiles.C b/Detectors/TPC/calibration/macro/prepareCMFiles.C index 08880ccbe4862..3bf18a9d14f8f 100644 --- a/Detectors/TPC/calibration/macro/prepareCMFiles.C +++ b/Detectors/TPC/calibration/macro/prepareCMFiles.C @@ -18,7 +18,7 @@ #include "TFile.h" #include "Framework/Logger.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/Mapper.h" #include "TPCBase/CalDet.h" #include "TPCBase/Utils.h" diff --git a/Detectors/TPC/calibration/macro/prepareITFiles.C b/Detectors/TPC/calibration/macro/prepareITFiles.C index eac0355e0ddfd..215ddb7909c8d 100644 --- a/Detectors/TPC/calibration/macro/prepareITFiles.C +++ b/Detectors/TPC/calibration/macro/prepareITFiles.C @@ -21,7 +21,7 @@ #include "TFile.h" #include "Framework/Logger.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/Mapper.h" #include "TPCBase/CalDet.h" #include "TPCBase/Utils.h" diff --git a/Detectors/TPC/calibration/macro/preparePedestalFiles.C b/Detectors/TPC/calibration/macro/preparePedestalFiles.C index 92bc1456e48d7..894827fffab1e 100644 --- a/Detectors/TPC/calibration/macro/preparePedestalFiles.C +++ b/Detectors/TPC/calibration/macro/preparePedestalFiles.C @@ -18,7 +18,7 @@ #include "TFile.h" #include "TROOT.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/Mapper.h" #include "TPCBase/CalDet.h" #include "TPCBase/Utils.h" 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 11f83f1c7189e..396214775eb76 100644 --- a/Detectors/TPC/calibration/src/CalculatedEdx.cxx +++ b/Detectors/TPC/calibration/src/CalculatedEdx.cxx @@ -21,7 +21,7 @@ #include "DataFormatsTPC/ClusterNative.h" #include "DetectorsBase/Propagator.h" #include "CCDB/BasicCCDBManager.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCReconstruction/TPCFastTransformHelperO2.h" #include "CalibdEdxTrackTopologyPol.h" #include "DataFormatsParameters/GRPMagField.h" @@ -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 93cdb7c47ee37..37400a28e4670 100644 --- a/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx +++ b/Detectors/TPC/calibration/src/CalibPadGainTracks.cxx @@ -19,10 +19,10 @@ #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 "GPUO2Interface.h" +#include "GPUO2ExternalUser.h" #include "DataFormatsTPC/ClusterNative.h" #include "DataFormatsTPC/VDriftCorrFact.h" #include "DetectorsBase/Propagator.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/CalibPadGainTracksBase.cxx b/Detectors/TPC/calibration/src/CalibPadGainTracksBase.cxx index 2d8c34810324b..8a2ad1df19200 100644 --- a/Detectors/TPC/calibration/src/CalibPadGainTracksBase.cxx +++ b/Detectors/TPC/calibration/src/CalibPadGainTracksBase.cxx @@ -15,7 +15,7 @@ #include "TPCCalibration/CalibPadGainTracksBase.h" #include "TPCCalibration/IDCDrawHelper.h" #include "TPCBase/ROC.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCCalibration/CalibTreeDump.h" #include "TPCBase/Mapper.h" diff --git a/Detectors/TPC/calibration/src/CalibdEdx.cxx b/Detectors/TPC/calibration/src/CalibdEdx.cxx index e1081335c04cb..5205c9348dec7 100644 --- a/Detectors/TPC/calibration/src/CalibdEdx.cxx +++ b/Detectors/TPC/calibration/src/CalibdEdx.cxx @@ -351,7 +351,7 @@ auto ProjectBoostHistoXFastAllSectors(const Hist& hist, std::vector& bin_in // access the bin content specified by bin_indices const float counts = hist.at(bin_indices); - float dEdx = hist.axis(ax::dEdx).value(i); + float dEdx = hist.axis(ax::dEdx).bin(i).center(); // scale the dedx to the mean if (stackMean != nullptr) { @@ -532,7 +532,7 @@ void CalibdEdx::fitHistGaus(TLinearFitter& fitter, CalibdEdxCorrection& corr, co LOGP(info, "Calibration fits took: {}", time.count()); } -void CalibdEdx::finalize(const bool useGausFits) +void CalibdEdx::finalize(const bool useGausFits, const bool averageSectors) { const float entries = minStackEntries(); mCalib.clear(); @@ -565,10 +565,15 @@ void CalibdEdx::finalize(const bool useGausFits) // get mean of each GEM stack CalibdEdxCorrection meanCorr{}; meanCorr.setDims(0); - TLinearFitter meanFitter(0); - meanFitter.SetFormula("1"); - // get the mean dEdx for each stack - fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses); + if (averageSectors) { + // set mean dEdx per stack to unity + meanCorr.setUnity(); + } else { + // get the mean dEdx for each stack + TLinearFitter meanFitter(0); + meanFitter.SetFormula("1"); + fitHist(mHist, meanCorr, meanFitter, mFitCut, mFitLowCutFactor, mFitPasses, nullptr, mDebugOutputStreamer.get()); + } if (!useGausFits) { // get higher dimension corrections with projected sectors fitHist(mHist, mCalib, fitter, mFitCut, mFitLowCutFactor, mFitPasses, &meanCorr, mDebugOutputStreamer.get()); @@ -585,7 +590,9 @@ int CalibdEdx::minStackEntries() const auto projection = bh::algorithm::project(mHist, std::vector{Axis::Sector, Axis::Stack, Axis::Charge}); auto dEdxCounts = bh::indexed(projection); // find the stack with the least number of entries - auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end()); + // use explicit int comparator to avoid ambiguous operator< between accessor and unlimited_storage::reference (boost/clang issue) + auto min_it = std::min_element(dEdxCounts.begin(), dEdxCounts.end(), + [](const auto& a, const auto& b) { return static_cast(*a) < static_cast(*b); }); return *min_it; } @@ -754,17 +761,28 @@ void CalibdEdx::dumpToFile(const char* outFile) CalibdEdx CalibdEdx::readFromFile(const char* inFile) { - TFile f(inFile, "READ"); - auto* obj = (CalibdEdx*)f.Get("calib"); + std::unique_ptr f(TFile::Open(inFile)); + if (!f || f->IsZombie()) { + LOGP(error, "Could not open file: {}", inFile); + CalibdEdx calTmp; + return calTmp; + } + + auto obj = f->Get("calib"); if (!obj) { + LOGP(error, "Could not read CalibdEdx object from file: {}", inFile); CalibdEdx calTmp; return calTmp; } + + THnF* hTmp = f->Get("histogram_data"); + CalibdEdx cal(*obj); - THnF* hTmp = (THnF*)f.Get("histogram_data"); + delete obj; + if (!hTmp) { - CalibdEdx calTmp; - return calTmp; + LOGP(warning, "Could not read histogram from file: {}. Returning empty histogram", inFile); + return cal; } cal.setFromRootHist(hTmp); return cal; 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 d1e1f60d4b801..c8bdfa0f99350 100644 --- a/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx +++ b/Detectors/TPC/calibration/src/CorrectionMapsLoader.cxx @@ -11,48 +11,65 @@ #include "TPCCalibration/CorrectionMapsLoader.h" #include "TPCCalibration/CorrMapParam.h" -#include "TPCReconstruction/TPCFastTransformHelperO2.h" -#include "TPCBase/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() == 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(); + if (canUseCTPScaling) { + LOGP(info, "Invalid TPC scaler value {} received for IDC-based scaling! Using CTP fallback", tpcScaler); + mIDC2CTPFallbackActive = true; + setMeanLumi(mCorrMap->getLumi(), false); + setMeanLumiRef(mCorrMapRef->getLumi()); + 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(); + LOGP(warning, "Invalid TPC scaler value {} received for IDC-based scaling! CTP fallback not possible, using stored IDC of {} from the map to avoid applying wrong corrections", tpcScaler, storedIDC); + setInstLumi(storedIDC); + } + } else { + if (mIDC2CTPFallbackActive) { + // reset back to normal operation + LOGP(info, "Valid TPC scaler value {} received, switching back to IDC-based scaling", tpcScaler); + mIDC2CTPFallbackActive = false; + setMeanLumi(mCorrMap->getIDC(), false); + setMeanLumiRef(mCorrMapRef->getIDC()); + setLumiScaleType(LumiScaleType::TPCScaler); + } + // correct IDC received + setInstLumi(tpcScaler); + } + } + if (getLumiCTPAvailable() && mInstCTPLumiOverride <= 0.) { if (pc.inputs().get>("CTPLumi").size() == sizeof(o2::ctp::LumiInfo)) { lumiPrev = lumiObj = pc.inputs().get("CTPLumi"); @@ -63,44 +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 (getLumiScaleType() == 2) { - float tpcScaler = pc.inputs().get("tpcscaler"); - setInstLumi(tpcScaler); - } - 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."; } @@ -109,49 +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)"}}); -} - -//________________________________________________________ -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"); - if (!tpcopt.requestCTPLumi && tpcopt.lumiType == 1) { - LOGP(fatal, "Scaling with CTP Lumi is requested but this input is disabled"); - } - return tpcopt; } //________________________________________________________ @@ -178,20 +141,23 @@ 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) { + checkMeanScaleConsistency(mapMeanRate, mCorrMap->getCTP2IDCFallBackThreshold()); + } if (getMeanLumiOverride() == 0 && mapMeanRate > 0.) { setMeanLumi(mapMeanRate, false); } @@ -204,20 +170,23 @@ 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) { + checkMeanScaleConsistency(mapRefMeanRate, mCorrMapRef->getCTP2IDCFallBackThreshold()); + } if (getMeanLumiRefOverride() == 0) { setMeanLumiRef(mapRefMeanRate); } @@ -241,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!"); @@ -254,78 +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 ((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"); } - - 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) +void CorrectionMapsLoader::checkMeanScaleConsistency(float meanLumi, float threshold) const { - 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(); -} - -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())); + 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() == LumiScaleType::TPCScaler) { + if (meanLumi > threshold) { + LOGP(fatal, "IDC scaling source is requested, but the map mean scale {} is above the threshold {}", meanLumi, threshold); } - TPCFastSpaceChargeCorrectionHelper::instance()->initInverse(corr, scaling, false); - } else { - LOGP(info, "Reinitializing inverse correction with lumi scale mode {} not supported for now", mLumiScaleMode); } } - -#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/IDCAverageGroup.cxx b/Detectors/TPC/calibration/src/IDCAverageGroup.cxx index f027a0a7d0056..63ab4d9e537ac 100644 --- a/Detectors/TPC/calibration/src/IDCAverageGroup.cxx +++ b/Detectors/TPC/calibration/src/IDCAverageGroup.cxx @@ -15,12 +15,13 @@ #include "TPCCalibration/IDCDrawHelper.h" #include "CommonUtils/TreeStreamRedirector.h" #include "TPCBase/Mapper.h" +#include "TPCBaseRecSim/PadFlags.h" #include "CommonConstants/MathConstants.h" // root includes #include "TFile.h" #include "TKey.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TH2Poly.h" #include "TCanvas.h" #include "TLatex.h" diff --git a/Detectors/TPC/calibration/src/IDCCCDBHelper.cxx b/Detectors/TPC/calibration/src/IDCCCDBHelper.cxx index a9fb8f0c4675f..189d1035fc767 100644 --- a/Detectors/TPC/calibration/src/IDCCCDBHelper.cxx +++ b/Detectors/TPC/calibration/src/IDCCCDBHelper.cxx @@ -18,7 +18,7 @@ #include "TPCBase/CalDet.h" #include "TPCBase/Mapper.h" #include "CommonUtils/TreeStreamRedirector.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TStyle.h" #include "TLine.h" diff --git a/Detectors/TPC/calibration/src/IDCDrawHelper.cxx b/Detectors/TPC/calibration/src/IDCDrawHelper.cxx index 3a0b11b4a3beb..a5181cc36706d 100644 --- a/Detectors/TPC/calibration/src/IDCDrawHelper.cxx +++ b/Detectors/TPC/calibration/src/IDCDrawHelper.cxx @@ -10,7 +10,7 @@ // or submit itself to any jurisdiction. #include "TPCCalibration/IDCDrawHelper.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCBase/Mapper.h" #include "TH2Poly.h" #include "TCanvas.h" diff --git a/Detectors/TPC/calibration/src/NeuralNetworkClusterizer.cxx b/Detectors/TPC/calibration/src/NeuralNetworkClusterizer.cxx deleted file mode 100644 index bfbb7afc946f8..0000000000000 --- a/Detectors/TPC/calibration/src/NeuralNetworkClusterizer.cxx +++ /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. - -/// \file NeuralNetworkClusterizer.cxx -/// \brief Fetching neural networks for clusterization from CCDB -/// \author Christian Sonnabend - -#include -#include "TPCCalibration/NeuralNetworkClusterizer.h" - -using namespace o2::tpc; - -void NeuralNetworkClusterizer::initCcdbApi(std::string url) -{ - ccdbApi.init(url); -} - -void NeuralNetworkClusterizer::loadIndividualFromCCDB(std::map settings) -{ - metadata["inputDType"] = settings["inputDType"]; - metadata["outputDType"] = settings["outputDType"]; - metadata["nnCCDBEvalType"] = settings["nnCCDBEvalType"]; // classification_1C, classification_2C, regression_1C, regression_2C - metadata["nnCCDBWithMomentum"] = settings["nnCCDBWithMomentum"]; // 0, 1 -> Only for regression model - metadata["nnCCDBLayerType"] = settings["nnCCDBLayerType"]; // FC, CNN - if (settings["nnCCDBInteractionRate"] != "" && std::stoi(settings["nnCCDBInteractionRate"]) > 0) { - metadata["nnCCDBInteractionRate"] = settings["nnCCDBInteractionRate"]; - } - if (settings["nnCCDBBeamType"] != "") { - metadata["nnCCDBBeamType"] = settings["nnCCDBBeamType"]; - } - - bool retrieveSuccess = ccdbApi.retrieveBlob(settings["nnCCDBPath"], settings["outputFolder"], metadata, 1, false, settings["outputFile"]); - // headers = ccdbApi.retrieveHeaders(settings["nnPathCCDB"], metadata, 1); // potentially needed to init some local variables - - if (retrieveSuccess) { - LOG(info) << "Network " << settings["nnCCDBPath"] << " retrieved from CCDB, stored at " << settings["outputFile"]; - } else { - LOG(error) << "Failed to retrieve network from CCDB"; - } -} diff --git a/Detectors/TPC/calibration/src/PressureTemperatureHelper.cxx b/Detectors/TPC/calibration/src/PressureTemperatureHelper.cxx index 2de4ee2086426..4f22ef8e35a03 100644 --- a/Detectors/TPC/calibration/src/PressureTemperatureHelper.cxx +++ b/Detectors/TPC/calibration/src/PressureTemperatureHelper.cxx @@ -14,7 +14,7 @@ /// \author Matthias Kleiner #include "TPCCalibration/PressureTemperatureHelper.h" -#include "TPCBase/CDBTypes.h" +#include "TPCBaseRecSim/CDBTypes.h" #include "Framework/ProcessingContext.h" #include "DataFormatsTPC/DCS.h" #include "Framework/InputRecord.h" diff --git a/Detectors/TPC/calibration/src/SACDrawHelper.cxx b/Detectors/TPC/calibration/src/SACDrawHelper.cxx index 9779681b464b7..db5a1efee209e 100644 --- a/Detectors/TPC/calibration/src/SACDrawHelper.cxx +++ b/Detectors/TPC/calibration/src/SACDrawHelper.cxx @@ -10,7 +10,7 @@ // or submit itself to any jurisdiction. #include "TPCCalibration/SACDrawHelper.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TH2Poly.h" #include "TCanvas.h" #include "TLatex.h" 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/calibration/src/VDriftHelper.cxx b/Detectors/TPC/calibration/src/VDriftHelper.cxx index 2badf3bb510e8..dc8f46af06828 100644 --- a/Detectors/TPC/calibration/src/VDriftHelper.cxx +++ b/Detectors/TPC/calibration/src/VDriftHelper.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCCalibration/VDriftHelper.h" #include "DataFormatsTPC/LtrCalibData.h" #include "TPCBase/ParameterGas.h" @@ -164,6 +164,10 @@ void VDriftHelper::extractCCDBInputs(ProcessingContext& pc, bool laser, bool its mIsTPScalingPossible = (vd.refTP > 0) || extractTPForVDrift(vd); } if (mIsTPScalingPossible) { + // if no new VDrift object was loaded and if delta TP is small, do not rescale and return + if (!mUpdated && std::abs(tp - vd.refTP) < 1e-5) { + return; + } mUpdated = true; vd.normalize(0, tp); if (vd.creationTime == saveVD.creationTime) { @@ -245,6 +249,15 @@ bool VDriftHelper::extractTPForVDrift(VDriftCorrFact& vdrift, int64_t tsStepMS) const int64_t tsStart = vdrift.firstTime; const int64_t tsEnd = vdrift.lastTime; + if (tsStart == tsEnd) { + static bool warned = false; + if (!warned) { + warned = true; + LOGP(warn, "VDriftHelper: Cannot extract T/P for VDrift with identical start/end time {}!", tsStart); + } + return false; + } + // make sanity check of the time range const auto [minValidTime, maxValidTime] = mPTHelper.getMinMaxTime(); const int64_t minTimeAccepted = static_cast(minValidTime) - 20 * o2::ccdb::CcdbObjectInfo::MINUTE; 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/dcs/macro/makeTPCCCDBEntryForDCS.C b/Detectors/TPC/dcs/macro/makeTPCCCDBEntryForDCS.C index edcb69907b3e5..d488aba14e264 100644 --- a/Detectors/TPC/dcs/macro/makeTPCCCDBEntryForDCS.C +++ b/Detectors/TPC/dcs/macro/makeTPCCCDBEntryForDCS.C @@ -14,6 +14,7 @@ #include #include "TFile.h" #include "CCDB/CcdbApi.h" +#include "CommonUtils/StringUtils.h" #include "DetectorsDCS/AliasExpander.h" #include "DetectorsDCS/DeliveryType.h" #include "DetectorsDCS/DataPointIdentifier.h" @@ -24,9 +25,10 @@ #include using DPID = o2::dcs::DataPointIdentifier; +using namespace o2::utils; /// macro to populate CCDB for TPC with the configuration for DCS -int makeTPCCCDBEntryForDCS(const std::string url = "http://localhost:8080") +int makeTPCCCDBEntryForDCS(const std::string url = "http://localhost:8080", std::string comment = "") { std::unordered_map dpid2DataDesc; @@ -64,9 +66,23 @@ int makeTPCCCDBEntryForDCS(const std::string url = "http://localhost:8080") o2::ccdb::CcdbApi api; api.init(url); // or http://localhost:8080 for a local installation - std::map md; + std::map meta; + + auto toKeyValPairs = [&meta](std::vector const& tokens) { + for (auto& token : tokens) { + auto keyval = Str::tokenize(token, '=', false); + if (keyval.size() != 2) { + LOG(error) << "Illegal command-line key/value string: " << token; + continue; + } + Str::trim(keyval[1]); + meta[keyval[0]] = keyval[1]; + } + }; + toKeyValPairs(Str::tokenize(comment, ';', true)); + long ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - api.storeAsTFileAny(&dpid2DataDesc, "TPC/Config/DCSDPconfig", md, ts, 99999999999999); + api.storeAsTFileAny(&dpid2DataDesc, "TPC/Config/DCSDPconfig", meta, ts, 99999999999999); return 0; } diff --git a/Detectors/TPC/dcs/src/DCSConfigSpec.cxx b/Detectors/TPC/dcs/src/DCSConfigSpec.cxx index dc13d4ed83081..05ac93ea5e216 100644 --- a/Detectors/TPC/dcs/src/DCSConfigSpec.cxx +++ b/Detectors/TPC/dcs/src/DCSConfigSpec.cxx @@ -38,7 +38,7 @@ #include "CCDB/CcdbApi.h" #include "CommonUtils/NameConf.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/CRUCalibHelpers.h" #include "TPCBase/FEEConfig.h" #include "TPCBase/FECInfo.h" diff --git a/Detectors/TPC/dcs/src/DCSSpec.cxx b/Detectors/TPC/dcs/src/DCSSpec.cxx index 1b64ff7a75ba4..ea4e3a29ff630 100644 --- a/Detectors/TPC/dcs/src/DCSSpec.cxx +++ b/Detectors/TPC/dcs/src/DCSSpec.cxx @@ -30,7 +30,7 @@ #include "DetectorsDCS/DeliveryType.h" #include "DetectorsDCS/AliasExpander.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCdcs/DCSProcessor.h" #include "TPCdcs/DCSSpec.h" diff --git a/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx b/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx index 8784f096e3202..0d032443c9754 100644 --- a/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx +++ b/Detectors/TPC/monitor/src/SimpleEventDisplayGUI.cxx @@ -45,7 +45,7 @@ #include "TPCBase/Mapper.h" #include "TPCBase/CalDet.h" #include "TPCBase/CalArray.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "DataFormatsTPC/Constants.h" #include "TPCMonitor/SimpleEventDisplayGUI.h" @@ -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/qc/CMakeLists.txt b/Detectors/TPC/qc/CMakeLists.txt index 60195ed6d451a..ce998eec6475c 100644 --- a/Detectors/TPC/qc/CMakeLists.txt +++ b/Detectors/TPC/qc/CMakeLists.txt @@ -77,6 +77,12 @@ o2_add_test(TrackClusters SOURCES test/test_TrackClusters.cxx LABELS tpc) +o2_add_test(DCSPTemperature + COMPONENT_NAME tpc + PUBLIC_LINK_LIBRARIES O2::TPCQC + SOURCES test/test_DCSPTemperature.cxx + LABELS tpc) + o2_add_test_root_macro(macro/runPID.C PUBLIC_LINK_LIBRARIES O2::TPCQC O2::DataFormatsTPC diff --git a/Detectors/TPC/qc/macro/runClusters.C b/Detectors/TPC/qc/macro/runClusters.C index ea1d1b54f429e..2fd4c919be321 100644 --- a/Detectors/TPC/qc/macro/runClusters.C +++ b/Detectors/TPC/qc/macro/runClusters.C @@ -18,7 +18,7 @@ #include "SimulationDataFormat/MCTruthContainer.h" #include "DataFormatsTPC/Constants.h" #include "TPCQC/Clusters.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #endif using namespace o2::tpc; diff --git a/Detectors/TPC/qc/macro/runPID.C b/Detectors/TPC/qc/macro/runPID.C index b015ac088334b..c693189a95652 100644 --- a/Detectors/TPC/qc/macro/runPID.C +++ b/Detectors/TPC/qc/macro/runPID.C @@ -25,7 +25,7 @@ #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsTPC/TrackCuts.h" #include "TPCBase/CalDet.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCBase/Utils.h" #include "DataFormatsTPC/ClusterNative.h" #include "TPCQC/PID.h" diff --git a/Detectors/TPC/qc/src/Clusters.cxx b/Detectors/TPC/qc/src/Clusters.cxx index 4bf59ced195ed..dc728a10a6570 100644 --- a/Detectors/TPC/qc/src/Clusters.cxx +++ b/Detectors/TPC/qc/src/Clusters.cxx @@ -18,7 +18,7 @@ // o2 includes #include "TPCQC/Clusters.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TPCBase/ROC.h" #include "TPCBase/CRU.h" #include "TPCBase/Mapper.h" diff --git a/Detectors/TPC/qc/src/IDCsVsSACs.cxx b/Detectors/TPC/qc/src/IDCsVsSACs.cxx index 55e93f580d8a4..604a1030c3d67 100644 --- a/Detectors/TPC/qc/src/IDCsVsSACs.cxx +++ b/Detectors/TPC/qc/src/IDCsVsSACs.cxx @@ -25,7 +25,7 @@ #include "TPCCalibration/SACFactorization.h" #include "TPCBase/CalDet.h" #include "TPCBase/Mapper.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" TCanvas* o2::tpc::qc::IDCsVsSACs::drawComparisionSACandIDCZero(TCanvas* outputCanvas, int nbins1D, float xMin1D, float xMax1D, int nbins1DSAC, float xMin1DSAC, float xMax1DSAC) const { 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/CTFCoder.h b/Detectors/TPC/reconstruction/include/TPCReconstruction/CTFCoder.h index ab49d0d49d79b..0d2d37a73d7db 100644 --- a/Detectors/TPC/reconstruction/include/TPCReconstruction/CTFCoder.h +++ b/Detectors/TPC/reconstruction/include/TPCReconstruction/CTFCoder.h @@ -119,10 +119,10 @@ struct MergedColumnsDecoder { } // namespace detail -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TPC) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TPC, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode compressed clusters to flat buffer diff --git a/Detectors/TPC/reconstruction/include/TPCReconstruction/ClusterContainer.h b/Detectors/TPC/reconstruction/include/TPCReconstruction/ClusterContainer.h deleted file mode 100644 index d86a845b0fe4c..0000000000000 --- a/Detectors/TPC/reconstruction/include/TPCReconstruction/ClusterContainer.h +++ /dev/null @@ -1,65 +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 ClusterContainer.h -/// \brief Container class for TPC clusters -#ifndef _ALICEO2_TPC_ClusterContainer_ -#define _ALICEO2_TPC_ClusterContainer_ - -#include -#include -#include // for Float_t etc - -namespace o2 -{ -namespace tpc -{ - -/// \class ClusterContainer -/// \brief Container class for TPC clusters -class ClusterContainer -{ - public: - // Initialize the clones array - // @param clusterType Possibility to store different types of clusters - // void InitArray(const Char_t* clusterType="o2::tpc::Cluster"); - - /// Add cluster to array - /// @param output, the vector to append to - /// @param cru CRU (sector) - /// @param row Row - /// @param q Total charge of cluster - /// @param qmax Maximum charge in a single cell (pad, time) - /// @param padmean Mean position of cluster in pad direction - /// @param padsigma Sigma of cluster in pad direction - /// @param timemean Mean position of cluster in time direction - /// @param timesigma Sigma of cluster in time direction - template - static ClusterType* addCluster(std::vector* output, - Int_t cru, Int_t row, Float_t qTot, Float_t qMax, - Float_t meanpad, Float_t meantime, Float_t sigmapad, - Float_t sigmatime) - { - assert(output); - output->emplace_back(); // emplace_back a defaut constructed cluster of type ClusterType - auto& cluster = output->back(); - // set its concrete parameters: - // ATTENTION: the order of parameters in setParameters is different than in AddCluster! - cluster.setParameters(cru, row, qTot, qMax, - meanpad, sigmapad, - meantime, sigmatime); - return &cluster; - } -}; -} // namespace tpc -} // namespace o2 - -#endif 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/src/TPCTrackingDigitsPreCheck.cxx b/Detectors/TPC/reconstruction/src/TPCTrackingDigitsPreCheck.cxx index 738e6cff20df4..c6f7000089d72 100644 --- a/Detectors/TPC/reconstruction/src/TPCTrackingDigitsPreCheck.cxx +++ b/Detectors/TPC/reconstruction/src/TPCTrackingDigitsPreCheck.cxx @@ -19,7 +19,7 @@ #include "DataFormatsTPC/Digit.h" #include "DataFormatsTPC/ClusterNative.h" -#include "GPUO2Interface.h" +#include "GPUO2ExternalUser.h" #include "GPUO2InterfaceConfiguration.h" #include "TPCBase/Sector.h" #include "Framework/Logger.h" diff --git a/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx b/Detectors/TPC/reconstruction/test/testGPUCATracking.cxx index bdf9b95e94450..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" @@ -50,36 +49,35 @@ BOOST_AUTO_TEST_CASE(CATracking_test1) { GPUO2Interface tracker; - float solenoidBz = -5.00668; //B-field - float refX = 1000.; //transport tracks to this x after tracking, >500 for disabling - bool continuous = false; //time frame data v.s. triggered events + float solenoidBz = -5.00668; // B-field + float refX = 83.; // transport tracks to this x after tracking, >500 for disabling + bool continuous = false; // time frame data v.s. triggered events GPUO2InterfaceConfiguration config; - config.configDeviceBackend.deviceType = GPUDataTypes::DeviceType::CPU; + config.configDeviceBackend.deviceType = gpudatatypes::DeviceType::CPU; config.configDeviceBackend.forceDeviceType = true; - config.configProcessing.ompThreads = 4; //4 threads if we run on the CPU, 1 = default, 0 = auto-detect - config.configProcessing.runQA = false; //Run QA after tracking - config.configProcessing.eventDisplay = nullptr; //Ptr to event display backend, for running standalone OpenGL event display + config.configProcessing.ompThreads = 4; // 4 threads if we run on the CPU, 1 = default, 0 = auto-detect + config.configProcessing.runQA = false; // Run QA after tracking + config.configProcessing.eventDisplay = nullptr; // Ptr to event display backend, for running standalone OpenGL event display config.configGRP.solenoidBzNominalGPU = solenoidBz; config.configGRP.grpContinuousMaxTimeBin = continuous ? GPUSettings::TPC_MAX_TF_TIME_BIN : 0; // Number of timebins in timeframe if continuous, 0 otherwise - config.configReconstruction.tpc.nWays = 3; //Should always be 3! - config.configReconstruction.tpc.nWaysOuter = true; //Will create outer param for TRD - config.configReconstruction.tpc.searchWindowDZDR = 2.5f; //Should always be 2.5 for looper-finding and/or continuous tracking + config.configReconstruction.tpc.nWays = 3; // Should always be 3! + config.configReconstruction.tpc.searchWindowDZDR = 2.5f; // Should always be 2.5 for looper-finding and/or continuous tracking config.configReconstruction.tpc.trackReferenceX = refX; - config.configWorkflow.steps.set(GPUDataTypes::RecoStep::TPCConversion, GPUDataTypes::RecoStep::TPCSectorTracking, - GPUDataTypes::RecoStep::TPCMerging, GPUDataTypes::RecoStep::TPCCompression, GPUDataTypes::RecoStep::TPCdEdx); - config.configWorkflow.inputs.set(GPUDataTypes::InOutType::TPCClusters); - config.configWorkflow.outputs.set(GPUDataTypes::InOutType::TPCMergedTracks); + config.configWorkflow.steps.set(gpudatatypes::RecoStep::TPCConversion, gpudatatypes::RecoStep::TPCSectorTracking, + gpudatatypes::RecoStep::TPCMerging, gpudatatypes::RecoStep::TPCCompression, gpudatatypes::RecoStep::TPCdEdx); + config.configWorkflow.inputs.set(gpudatatypes::InOutType::TPCClusters); + config.configWorkflow.outputs.set(gpudatatypes::InOutType::TPCMergedTracks); + + auto fastTransformTmp = TPCFastTransformHelperO2::instance()->create(0); + aligned_unique_buffer_ptr fastTransformBuf; + TPCFastTransformPOD::create(fastTransformBuf, *fastTransformTmp); + config.configCalib.fastTransform = fastTransformBuf.get(); - 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 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/simulation/include/TPCSimulation/GEMAmplification.h b/Detectors/TPC/simulation/include/TPCSimulation/GEMAmplification.h index f5c40569fee43..8dbfa21febc69 100644 --- a/Detectors/TPC/simulation/include/TPCSimulation/GEMAmplification.h +++ b/Detectors/TPC/simulation/include/TPCSimulation/GEMAmplification.h @@ -118,8 +118,10 @@ inline int GEMAmplification::getStackAmplification(const CRU& cru, const PadPos& break; } case AmplificationMode::EffectiveMode: { + const int region = static_cast(cru.gemStack()); + const float relativeGain = mGEMParam->RelativeGainStack[region]; return static_cast(static_cast(getEffectiveStackAmplification(nElectrons)) * - mGainMap->getValue(cru, pos.getRow(), pos.getPad())); + mGainMap->getValue(cru, pos.getRow(), pos.getPad()) * relativeGain); break; } } diff --git a/Detectors/TPC/simulation/include/TPCSimulation/SAMPAProcessing.h b/Detectors/TPC/simulation/include/TPCSimulation/SAMPAProcessing.h index be40c8652ad61..3f21b6723be14 100644 --- a/Detectors/TPC/simulation/include/TPCSimulation/SAMPAProcessing.h +++ b/Detectors/TPC/simulation/include/TPCSimulation/SAMPAProcessing.h @@ -158,23 +158,19 @@ template inline float SAMPAProcessing::makeSignal(float ADCcounts, const int sector, const int globalPadInSector, const float commonMode, float& pedestal, float& noise, float tot) { - float signal = ADCcounts; pedestal = getPedestal(sector, globalPadInSector); - float pedestalCRU = getPedestalCRU(sector, globalPadInSector); noise = getNoise(sector, globalPadInSector); + + const float signal = ADCcounts; + const float pedestalCRU = getPedestalCRU(sector, globalPadInSector); + const float fullSignal = signal - commonMode + noise + pedestal + (tot > 0 ? 80 : 0); // TODO: improve to also add tail + switch (MODE) { case DigitzationMode::FullMode: { - signal -= commonMode; - signal += noise; - signal += pedestal; - return getADCSaturation(signal); + return getADCSaturation(fullSignal); break; } case DigitzationMode::ZeroSuppression: { - signal -= commonMode; - signal += noise; - signal += pedestal; - signal += (tot > 0) ? 80 : 0; // TODO: improve to also add tail const float signalSubtractPedestal = getADCSaturation(signal) - pedestalCRU; const float zeroSuppression = getZeroSuppression(sector, globalPadInSector); if (signalSubtractPedestal < zeroSuppression) { @@ -184,10 +180,10 @@ inline float SAMPAProcessing::makeSignal(float ADCcounts, const int sector, cons break; } case DigitzationMode::ZeroSuppressionCMCorr: { - signal += noise; - signal += pedestal; - signal += (tot > 0) ? 80 : 0; // TODO: improve to also add tail - const float signalSubtractPedestal = getADCSaturation(signal) - pedestalCRU; + // TODO: this is not really a common mode correction, since the common mode is simply not treated. + // Instead, the full common mode algorithm should be implemented and used + const float signalNoCM = signal + noise + pedestal + (tot > 0 ? 80 : 0); // TODO: improve to also add tail + const float signalSubtractPedestal = getADCSaturation(signalNoCM) - pedestalCRU; const float zeroSuppression = getZeroSuppression(sector, globalPadInSector); if (signalSubtractPedestal < zeroSuppression) { return 0.f; @@ -196,18 +192,12 @@ inline float SAMPAProcessing::makeSignal(float ADCcounts, const int sector, cons break; } case DigitzationMode::SubtractPedestal: { - signal -= commonMode; - signal += noise; - signal += pedestal; - const float signalSubtractPedestal = getADCSaturation(signal) - pedestalCRU; + const float signalSubtractPedestal = getADCSaturation(fullSignal) - pedestalCRU; return signalSubtractPedestal; break; } case DigitzationMode::NoSaturation: { - signal -= commonMode; - signal += noise; - signal += pedestal; - return signal; + return fullSignal; break; } case DigitzationMode::PropagateADC: { diff --git a/Detectors/TPC/simulation/macro/toyCluster.C b/Detectors/TPC/simulation/macro/toyCluster.C index d60e5a7c0f94e..7baeef1cb1a6b 100644 --- a/Detectors/TPC/simulation/macro/toyCluster.C +++ b/Detectors/TPC/simulation/macro/toyCluster.C @@ -44,7 +44,7 @@ #include "TPCBase/Mapper.h" #include "TPCBase/ParameterDetector.h" #include "TPCBase/ParameterElectronics.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCSimulation/ElectronTransport.h" #include "TPCSimulation/SAMPAProcessing.h" #include "TPCSimulation/Point.h" diff --git a/Detectors/TPC/simulation/src/DigitContainer.cxx b/Detectors/TPC/simulation/src/DigitContainer.cxx index c2e7226706eb2..dfff4b91d6451 100644 --- a/Detectors/TPC/simulation/src/DigitContainer.cxx +++ b/Detectors/TPC/simulation/src/DigitContainer.cxx @@ -17,7 +17,7 @@ #include #include #include "TPCBase/Mapper.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/ParameterElectronics.h" #include "TPCBase/IonTailSettings.h" #include "SimConfig/DigiParams.h" diff --git a/Detectors/TPC/simulation/src/Digitizer.cxx b/Detectors/TPC/simulation/src/Digitizer.cxx index cb865d9f7f752..49abc0a0b99af 100644 --- a/Detectors/TPC/simulation/src/Digitizer.cxx +++ b/Detectors/TPC/simulation/src/Digitizer.cxx @@ -24,7 +24,7 @@ #include "TPCSimulation/GEMAmplification.h" #include "TPCSimulation/Point.h" #include "TPCSimulation/SAMPAProcessing.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCSpaceCharge/SpaceCharge.h" #include "TPCBase/Mapper.h" #include "TPCCalibration/CorrMapParam.h" diff --git a/Detectors/TPC/simulation/src/ElectronTransport.cxx b/Detectors/TPC/simulation/src/ElectronTransport.cxx index f6b6f906ce862..f9e36aa642158 100644 --- a/Detectors/TPC/simulation/src/ElectronTransport.cxx +++ b/Detectors/TPC/simulation/src/ElectronTransport.cxx @@ -14,7 +14,7 @@ /// \author Andi Mathis, TU München, andreas.mathis@ph.tum.de #include "TPCSimulation/ElectronTransport.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include diff --git a/Detectors/TPC/simulation/src/GEMAmplification.cxx b/Detectors/TPC/simulation/src/GEMAmplification.cxx index 2dc363bf151b4..8d47464e9ef53 100644 --- a/Detectors/TPC/simulation/src/GEMAmplification.cxx +++ b/Detectors/TPC/simulation/src/GEMAmplification.cxx @@ -17,7 +17,7 @@ #include #include "MathUtils/CachingTF1.h" #include -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include #include "Framework/Logger.h" #include diff --git a/Detectors/TPC/simulation/src/IDCSim.cxx b/Detectors/TPC/simulation/src/IDCSim.cxx index 45597393d8f2a..3958115d95f7c 100644 --- a/Detectors/TPC/simulation/src/IDCSim.cxx +++ b/Detectors/TPC/simulation/src/IDCSim.cxx @@ -16,7 +16,7 @@ #include "TPCBase/Mapper.h" #include #include "Framework/Logger.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "TH2Poly.h" #include "TCanvas.h" #include "TLatex.h" diff --git a/Detectors/TPC/simulation/src/SAMPAProcessing.cxx b/Detectors/TPC/simulation/src/SAMPAProcessing.cxx index 83f50832abfac..462008846fa04 100644 --- a/Detectors/TPC/simulation/src/SAMPAProcessing.cxx +++ b/Detectors/TPC/simulation/src/SAMPAProcessing.cxx @@ -14,7 +14,7 @@ /// \author Andi Mathis, TU München, andreas.mathis@ph.tum.de #include "TPCSimulation/SAMPAProcessing.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include #include diff --git a/Detectors/TPC/simulation/test/testTPCDigitContainer.cxx b/Detectors/TPC/simulation/test/testTPCDigitContainer.cxx index 72e4dfaf6a0b2..73fca084507e5 100644 --- a/Detectors/TPC/simulation/test/testTPCDigitContainer.cxx +++ b/Detectors/TPC/simulation/test/testTPCDigitContainer.cxx @@ -23,7 +23,7 @@ #include "DataFormatsTPC/Digit.h" #include "TPCSimulation/DigitContainer.h" #include "TPCSimulation/SAMPAProcessing.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" namespace o2 { diff --git a/Detectors/TPC/simulation/test/testTPCElectronTransport.cxx b/Detectors/TPC/simulation/test/testTPCElectronTransport.cxx index 12732a52d7fa7..e42e60d5edabb 100644 --- a/Detectors/TPC/simulation/test/testTPCElectronTransport.cxx +++ b/Detectors/TPC/simulation/test/testTPCElectronTransport.cxx @@ -20,7 +20,7 @@ #include "TPCSimulation/ElectronTransport.h" #include "TPCBase/ParameterGas.h" #include "TPCBase/ParameterDetector.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TH1D.h" #include "TF1.h" diff --git a/Detectors/TPC/simulation/test/testTPCGEMAmplification.cxx b/Detectors/TPC/simulation/test/testTPCGEMAmplification.cxx index 63c092deb59c2..8a3ce711b52ef 100644 --- a/Detectors/TPC/simulation/test/testTPCGEMAmplification.cxx +++ b/Detectors/TPC/simulation/test/testTPCGEMAmplification.cxx @@ -20,7 +20,7 @@ #include "TPCSimulation/GEMAmplification.h" #include "TPCBase/ParameterGas.h" #include "TPCBase/ParameterGEM.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TH1D.h" #include "TF1.h" diff --git a/Detectors/TPC/simulation/test/testTPCSAMPAProcessing.cxx b/Detectors/TPC/simulation/test/testTPCSAMPAProcessing.cxx index a89ea335d60b5..05ed4393ea65c 100644 --- a/Detectors/TPC/simulation/test/testTPCSAMPAProcessing.cxx +++ b/Detectors/TPC/simulation/test/testTPCSAMPAProcessing.cxx @@ -18,7 +18,7 @@ #define BOOST_TEST_DYN_LINK #include #include "TPCSimulation/SAMPAProcessing.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include #include diff --git a/Detectors/TPC/spacecharge/CMakeLists.txt b/Detectors/TPC/spacecharge/CMakeLists.txt index a2f4cdb51becb..390e6c99c9c7e 100644 --- a/Detectors/TPC/spacecharge/CMakeLists.txt +++ b/Detectors/TPC/spacecharge/CMakeLists.txt @@ -15,7 +15,7 @@ o2_add_library(TPCSpaceCharge src/PoissonSolver.cxx src/TriCubic.cxx src/DataContainer3D.cxx - PUBLIC_LINK_LIBRARIES O2::TPCBase + PUBLIC_LINK_LIBRARIES O2::TPCBaseRecSim O2::Field Vc::Vc ROOT::Core diff --git a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/DataContainer3D.h b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/DataContainer3D.h index 79400c3d3d214..73638c5b2982f 100644 --- a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/DataContainer3D.h +++ b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/DataContainer3D.h @@ -186,6 +186,9 @@ struct DataContainer3D { /// print the matrix void print() const; + /// convert a data container to a new datacontainer with different grid definition (e.g. different number of vertices) + DataContainer3D convert(const o2::tpc::RegularGrid3D& gridNew, const o2::tpc::RegularGrid3D& gridRef, const int threads = 1) const; + /// operator overload DataContainer3D& operator*=(const DataT value); DataContainer3D& operator+=(const DataContainer3D& other); diff --git a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolverHelpers.h b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolverHelpers.h index 218bddcf49402..933273d0b5eb9 100644 --- a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolverHelpers.h +++ b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/PoissonSolverHelpers.h @@ -19,6 +19,7 @@ #define ALICEO2_TPC_POISSONSOLVERHELPERS_H_ #include "CommonConstants/MathConstants.h" +#include "DataFormatsTPC/Defs.h" namespace o2 { @@ -55,7 +56,7 @@ struct MGParameters { ///< Parameter inline static int nMGCycle = 200; ///< number of multi grid cycle (V type) inline static int maxLoop = 7; ///< the number of tree-deep of multi grid inline static int gamma = 1; ///< number of iteration at coarsest level !TODO SET TO REASONABLE VALUE! - inline static bool normalizeGridToOneSector = false; ///< the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the potential for phi symmetric sc density or boundary potentials + inline static int normalizeGridToNSector = SECTORSPERSIDE; ///< the grid in phi direction is squashed from 2 Pi to (2 Pi / SECTORSPERSIDE). This can used to get the potential for phi symmetric sc density or boundary potentials }; template diff --git a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/SpaceCharge.h b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/SpaceCharge.h index ad76ec2b6da5b..d0c66d0ff3df1 100644 --- a/Detectors/TPC/spacecharge/include/TPCSpaceCharge/SpaceCharge.h +++ b/Detectors/TPC/spacecharge/include/TPCSpaceCharge/SpaceCharge.h @@ -204,10 +204,13 @@ class SpaceCharge /// simulate only one sector instead of 18 per side. This makes currently only sense for the static distortions (ToDo: simplify usage) /// phi max will be restricted to 2Pi/18 for this instance and for global instance of poisson solver - void setSimOneSector(); + void setSimOneSector() { setSimNSector(1); } + + /// simulate N sectors + void setSimNSector(const int nSectors); /// unsetting simulation of one sector - static void unsetSimOneSector(); + static void unsetSimNSector(); /// setting default potential (same potential for all GEM frames. The default value of 1000V are matched to distortions observed in laser data without X-Ray etc. /// \param side side of the TPC where the potential will be set @@ -308,10 +311,24 @@ class SpaceCharge /// scaling the space-charge density for given stack void scaleChargeDensityStack(const float scalingFactor, const Sector sector, const GEMstack stack); + /// scale the potential by a scaling factor + /// \param scalingFactor factor to scale the potential + /// \param side side for which the potential will be scaled + void scalePotential(const DataT scalingFactor, const Side side) { mPotential[side] *= scalingFactor; } + /// add space charge density from other object (this.mDensity = this.mDensity + other.mDensity) /// \param otherSC other space-charge object, which charge will be added to current object void addChargeDensity(const SpaceCharge& otherSC); + /// add global corrections from other space charge object + void addGlobalCorrections(const SpaceCharge& otherSC, const Side side); + + /// convert space-charge object to new definition of number of vertices + /// \param nZNew new number of vertices in z direction + /// \param nRNew new number of vertices in r direction + /// \param nPhiNew new number of vertices in phi direction + void downSampleObject(const int nZNew, const int nRNew, const int nPhiNew); + /// step 3: calculate the local distortions and corrections with an electric field /// \param type calculate local corrections or local distortions: type = o2::tpc::SpaceCharge<>::Type::Distortions or o2::tpc::SpaceCharge<>::Type::Corrections /// \param formulaStruct struct containing a method to evaluate the electric field Er, Ez, Ephi (analytical formula or by TriCubic interpolator) @@ -415,6 +432,9 @@ class SpaceCharge /// \param phi global phi coordinate DataT getDensityCyl(const DataT z, const DataT r, const DataT phi, const Side side) const; + /// get the potential for list of given coordinate + std::vector getDensityCyl(const std::vector& z, const std::vector& r, const std::vector& phi, const Side side) const; + /// get the potential for given coordinate /// \param z global z coordinate /// \param r global r coordinate @@ -1184,6 +1204,10 @@ class SpaceCharge /// \param gCorr function returning global corrections for given global coordinate void setGlobalCorrections(const std::function& gCorr, const Side side); + /// setting the global distortions directly from input function provided in global coordinates + /// \param gDist function returning global distortions for given global coordinate + void setGlobalDistortions(const std::function& gDist, const Side side); + /// set misalignment of ROC for shift in z /// \param sector sector for which the misalignment in z will be applied (if sector=-1 all sectors are shifted) /// \param type 0=IROC, 1=OROC, 2=IROC+OROC @@ -1229,7 +1253,16 @@ class SpaceCharge /// \param tgl tgl of the track /// \param nPoints number of points used to calculate the DCAr /// \param pcstream if provided debug output is being created - float getDCAr(float tgl, const int nPoints, const float phi, o2::utils::TreeStreamRedirector* pcstream = nullptr) const; + float getDCAr(float tgl, const int nPoints, const float phi, float rStart = -1, o2::utils::TreeStreamRedirector* pcstream = nullptr) const; + + /// \return returns nearest phi vertex for given phi position + size_t getNearestPhiVertex(const DataT phi, const Side side) const { return std::round(phi / getGridSpacingPhi(side)); } + + /// \return returns nearest r vertex for given radius position + size_t getNearestRVertex(const DataT r, const Side side) const { return std::round((r - getRMin(side)) / getGridSpacingR(side) + 0.5); } + + /// \return returns number of bins in phi direction for the gap between sectors and for the GEM frame + size_t getPhiBinsGapFrame(const Side side) const; private: ParamSpaceCharge mParamGrid{}; ///< parameters of the grid on which the calculations are performed @@ -1352,15 +1385,6 @@ class SpaceCharge /// dump the created electron tracks with calculateElectronDriftPath function to a tree void dumpElectronTracksToTree(const std::vector>, std::array>>& electronTracks, const int nSamplingPoints, const char* outFile) const; - /// \return returns nearest phi vertex for given phi position - size_t getNearestPhiVertex(const DataT phi, const Side side) const { return std::round(phi / getGridSpacingPhi(side)); } - - /// \return returns nearest r vertex for given radius position - size_t getNearestRVertex(const DataT r, const Side side) const { return std::round((r - getRMin(side)) / getGridSpacingR(side) + 0.5); } - - /// \return returns number of bins in phi direction for the gap between sectors and for the GEM frame - size_t getPhiBinsGapFrame(const Side side) const; - /// \return setting the boundary potential for given GEM stack void setPotentialBoundaryGEMFrameAlongPhi(const std::function& potentialFunc, const GEMstack stack, const bool bottom, const Side side, const bool outerFrame = false); @@ -1372,16 +1396,16 @@ class SpaceCharge void initAllBuffers(); - void setBoundaryFromIndices(const std::function& potentialFunc, const std::vector& indices, const Side side); + void setBoundaryFromIndices(const std::function& potentialFunc, const std::vector>& indices, const Side side); /// get indices of the GEM frame along r - std::vector getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const; + std::vector> getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const; /// get indices of the GEM frame along phi - std::vector getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap = false) const; + std::vector> getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap = false) const; void setROCMisalignment(int stackType, int misalignmentType, int sector, const float potMin, const float potMax); - void fillROCMisalignment(const std::vector& indicesTop, const std::vector& indicesBottom, int sector, int misalignmentType, const std::pair& deltaPotPar); + void fillROCMisalignment(const std::vector>& indicesTop, const std::vector>& indicesBottom, int sector, int misalignmentType, const std::pair& deltaPotPar); /// set potentialsdue to ROD misalignment void initRodAlignmentVoltages(const MisalignmentType misalignmentType, const FCType fcType, const int sector, const Side side, const float deltaPot); @@ -1389,6 +1413,8 @@ class SpaceCharge void calcGlobalDistCorrIterative(const DistCorrInterpolator& globCorr, const int maxIter, const DataT approachZ, const DataT approachR, const DataT approachPhi, const DataT diffCorr, const SpaceCharge* scSCale, float scale, const Type type); void calcGlobalDistCorrIterativeLinearCartesian(const DistCorrInterpolator& globCorr, const int maxIter, const DataT approachX, const DataT approachY, const DataT approachZ, const DataT diffCorr, const SpaceCharge* scSCale, float scale, const Type type); + void setGlobalDistCorr(const Type type, const std::function& gFunc, const Side side); + ClassDefNV(SpaceCharge, 6); }; diff --git a/Detectors/TPC/spacecharge/macro/createSCHistosFromHits.C b/Detectors/TPC/spacecharge/macro/createSCHistosFromHits.C index f6232018f3c59..cf4e5b2719b22 100644 --- a/Detectors/TPC/spacecharge/macro/createSCHistosFromHits.C +++ b/Detectors/TPC/spacecharge/macro/createSCHistosFromHits.C @@ -112,7 +112,7 @@ g++ -o createSCHistosFromHits createSCHistosFromHits.C -I ~/alice/sw/osx_x86-64/ #include "TPCBase/ParameterDetector.h" #include "TPCBase/ParameterGEM.h" #include "TPCBase/ParameterElectronics.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCSimulation/ElectronTransport.h" #include "TPCSimulation/GEMAmplification.h" #include "TPCSimulation/SAMPAProcessing.h" diff --git a/Detectors/TPC/spacecharge/src/DataContainer3D.cxx b/Detectors/TPC/spacecharge/src/DataContainer3D.cxx index cd2802b975fd2..60d7c28b8c74e 100644 --- a/Detectors/TPC/spacecharge/src/DataContainer3D.cxx +++ b/Detectors/TPC/spacecharge/src/DataContainer3D.cxx @@ -331,7 +331,6 @@ void DataContainer3D::dumpSlice(std::string_view treename, std::string_vi ROOT::RDataFrame dFrame(treename, fileIn); auto df = dFrame.Define("slice", [rangeiZ, rangeiR, rangeiPhi](const std::pair>& values, unsigned short nz, unsigned short nr, unsigned short nphi) { - const bool simOneSectorOnly = MGParameters::normalizeGridToOneSector; std::vector ir; std::vector iphi; std::vector iz; @@ -370,12 +369,12 @@ void DataContainer3D::dumpSlice(std::string_view treename, std::string_vi const float rTmp = o2::tpc::GridProperties::getRMin() + o2::tpc::GridProperties::getGridSpacingR(nr) * iRTmp; const float zTmp = o2::tpc::GridProperties::getZMin() + o2::tpc::GridProperties::getGridSpacingZ(nz) * iZTmp; - const float phiTmp = o2::tpc::GridProperties::getPhiMin() + o2::tpc::GridProperties::getGridSpacingPhi(nphi) / (simOneSectorOnly ? SECTORSPERSIDE : 1) * iPhiTmp; + const float phiTmp = o2::tpc::GridProperties::getPhiMin() + o2::tpc::GridProperties::getGridSpacingPhi(nphi) * (MGParameters::normalizeGridToNSector / double(SECTORSPERSIDE)) * iPhiTmp; const float x = rTmp * std::cos(phiTmp); const float y = rTmp * std::sin(phiTmp); const LocalPosition3D pos(x, y, zTmp); - unsigned char secNum = simOneSectorOnly ? 0 : std::floor(phiTmp / SECPHIWIDTH); + unsigned char secNum = std::floor(phiTmp / SECPHIWIDTH); Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE); LocalPosition3D lPosTmp = Mapper::GlobalToLocal(pos, sector); @@ -428,10 +427,9 @@ void DataContainer3D::dumpInterpolation(std::string_view treename, std::s // define grid for interpolation using GridProp = GridProperties; - const RegularGrid3D mGrid3D(GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, GridProp::getGridSpacingZ(nz), GridProp::getGridSpacingR(nr), o2::tpc::GridProperties::getGridSpacingPhi(nphi) / (MGParameters::normalizeGridToOneSector ? SECTORSPERSIDE : 1), ParamSpaceCharge{nr, nz, nphi}); + const RegularGrid3D mGrid3D(GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, GridProp::getGridSpacingZ(nz), GridProp::getGridSpacingR(nr), o2::tpc::GridProperties::getGridSpacingPhi(nphi) * (MGParameters::normalizeGridToNSector / double(SECTORSPERSIDE)), ParamSpaceCharge{nr, nz, nphi}); auto interpolate = [&mGrid3D = std::as_const(mGrid3D), &data = std::as_const(data), rangeR, rangeZ, rangePhi, nR, nZ, nPhi](unsigned int, ULong64_t iPhi) { - const bool simOneSectorOnly = MGParameters::normalizeGridToOneSector; std::vector ir; std::vector iphi; std::vector iz; @@ -473,7 +471,7 @@ void DataContainer3D::dumpInterpolation(std::string_view treename, std::s const float x = rPos * std::cos(phiPos); const float y = rPos * std::sin(phiPos); const LocalPosition3D pos(x, y, zPos); - unsigned char secNum = simOneSectorOnly ? 0 : std::floor(phiPos / SECPHIWIDTH); + unsigned char secNum = std::floor(phiPos / SECPHIWIDTH); // TODO CHECK THIS Sector sector(secNum + (pos.Z() < 0) * SECTORSPERSIDE); LocalPosition3D lPosTmp = Mapper::GlobalToLocal(pos, sector); lPos.emplace_back(lPosTmp); @@ -512,6 +510,27 @@ bool DataContainer3D::getVertices(std::string_view treename, std::string_ return true; } +template +DataContainer3D DataContainer3D::convert(const o2::tpc::RegularGrid3D& gridNew, const o2::tpc::RegularGrid3D& gridRef, const int threads) const +{ + const int nZNew = gridNew.getNZ(); + const int nRNew = gridNew.getNR(); + const int nPhiNew = gridNew.getNPhi(); + DataContainer3D contCont(nZNew, nRNew, nPhiNew); +#pragma omp parallel for num_threads(threads) + for (size_t iPhi = 0; iPhi < nPhiNew; ++iPhi) { + const DataT phi = gridNew.getPhiVertex(iPhi); + for (size_t iR = 0; iR < nRNew; ++iR) { + const DataT radius = gridNew.getRVertex(iR); + for (size_t iZ = 0; iZ < nZNew; ++iZ) { + const DataT z = gridNew.getZVertex(iZ); + contCont(iZ, iR, iPhi) = interpolate(z, radius, phi, gridRef); + } + } + } + return contCont; +} + template class o2::tpc::DataContainer3D; template class o2::tpc::DataContainer3D; diff --git a/Detectors/TPC/spacecharge/src/PoissonSolver.cxx b/Detectors/TPC/spacecharge/src/PoissonSolver.cxx index 952f4b29111ce..d00e268f64125 100644 --- a/Detectors/TPC/spacecharge/src/PoissonSolver.cxx +++ b/Detectors/TPC/spacecharge/src/PoissonSolver.cxx @@ -940,7 +940,7 @@ void PoissonSolver::residue2D(Vector& residue, const Vector& matricesCurr for (int j = 1; j < tnZColumn - 1; ++j) { residue(i, j, iPhi) = ih2 * (coefficient1[i] * matricesCurrentV(i + 1, j, iPhi) + coefficient2[i] * matricesCurrentV(i - 1, j, iPhi) + tempRatio * (matricesCurrentV(i, j + 1, iPhi) + matricesCurrentV(i, j - 1, iPhi)) - inverseTempFourth * matricesCurrentV(i, j, iPhi)) + matricesCurrentCharge(i, j, iPhi); } // end cols - } // end nRRow + } // end nRRow // Boundary points. for (int i = 0; i < tnRRow; ++i) { @@ -997,7 +997,7 @@ void PoissonSolver::residue3D(Vector& residue, const Vector& matricesCurr coefficient3[i] * (signPlus * matricesCurrentV(i, j, mp1) + signMinus * matricesCurrentV(i, j, mm1)) - inverseCoefficient4[i] * matricesCurrentV(i, j, m)) + matricesCurrentCharge(i, j, m); } // end cols - } // end mParamGrid.NRVertices + } // end mParamGrid.NRVertices } } @@ -1263,9 +1263,9 @@ void PoissonSolver::relax3D(Vector& matricesCurrentV, const Vector& matri for (int i = isw; i < tnRRow - 1; i += 2) { (matricesCurrentV)(i, j, m) = (coefficient2[i] * (matricesCurrentV)(i - 1, j, m) + tempRatioZ * ((matricesCurrentV)(i, j - 1, m) + (matricesCurrentV)(i, j + 1, m)) + coefficient1[i] * (matricesCurrentV)(i + 1, j, m) + coefficient3[i] * (signPlus * (matricesCurrentV)(i, j, mp1) + signMinus * (matricesCurrentV)(i, j, mm1)) + (h2 * (matricesCurrentCharge)(i, j, m))) * coefficient4[i]; } // end cols - } // end mParamGrid.NRVertices - } // end phi - } // end sweep + } // end mParamGrid.NRVertices + } // end phi + } // end sweep } else if (MGParameters::relaxType == RelaxType::Jacobi) { // for each slice for (int m = 0; m < iPhi; ++m) { @@ -1306,8 +1306,8 @@ void PoissonSolver::relax3D(Vector& matricesCurrentV, const Vector& matri for (int i = 1; i < tnRRow - 1; ++i) { (matricesCurrentV)(i, j, m) = (coefficient2[i] * (matricesCurrentV)(i - 1, j, m) + tempRatioZ * ((matricesCurrentV)(i, j - 1, m) + (matricesCurrentV)(i, j + 1, m)) + coefficient1[i] * (matricesCurrentV)(i + 1, j, m) + coefficient3[i] * (signPlus * (matricesCurrentV)(i, j, mp1) + signMinus * (matricesCurrentV)(i, j, mm1)) + (h2 * (matricesCurrentCharge)(i, j, m))) * coefficient4[i]; } // end cols - } // end mParamGrid.NRVertices - } // end phi + } // end mParamGrid.NRVertices + } // end phi } else { // Case weighted Jacobi // TODO @@ -1329,15 +1329,15 @@ void PoissonSolver::relax2D(Vector& matricesCurrentV, const Vector& matri matricesCurrentV(i, j, iPhi) = tempFourth * (coefficient1[i] * matricesCurrentV(i + 1, j, iPhi) + coefficient2[i] * matricesCurrentV(i - 1, j, iPhi) + tempRatio * (matricesCurrentV(i, j + 1, iPhi) + matricesCurrentV(i, j - 1, iPhi)) + (h2 * matricesCurrentCharge(i, j, iPhi))); } // end cols - } // end mParamGrid.NRVertices - } // end pass red-black + } // end mParamGrid.NRVertices + } // end pass red-black } else if (MGParameters::relaxType == RelaxType::Jacobi) { for (int j = 1; j < tnZColumn - 1; ++j) { for (int i = 1; i < tnRRow - 1; ++i) { matricesCurrentV(i, j, iPhi) = tempFourth * (coefficient1[i] * matricesCurrentV(i + 1, j, iPhi) + coefficient2[i] * matricesCurrentV(i - 1, j, iPhi) + tempRatio * (matricesCurrentV(i, j + 1, iPhi) + matricesCurrentV(i, j - 1, iPhi)) + (h2 * matricesCurrentCharge(i, j, iPhi))); } // end cols - } // end mParamGrid.NRVertices + } // end mParamGrid.NRVertices } else if (MGParameters::relaxType == RelaxType::WeightedJacobi) { // Weighted Jacobi // TODO @@ -1421,7 +1421,7 @@ void PoissonSolver::restrict3D(Vector& matricesCurrentCharge, const Vecto matricesCurrentCharge(i, j, m) = residue(ii, jj, mm) / 8 + s1 / 16 + s2 / 32 + s3 / 64; } // end cols - } // end mParamGrid.NRVertices + } // end mParamGrid.NRVertices // for boundary for (int j = 0, jj = 0; j < tnZColumn; ++j, jj += 2) { @@ -1460,7 +1460,7 @@ void PoissonSolver::restrict2D(Vector& matricesCurrentCharge, const Vecto (residue(iip1, jjp1, iphi) + residue(iim1, jjp1, iphi) + residue(iip1, jjm1, iphi) + residue(iim1, jjm1, iphi)) / 16; } } // end cols - } // end mParamGrid.NRVertices + } // end mParamGrid.NRVertices // boundary // for boundary for (int j = 0, jj = 0; j < tnZColumn; ++j, jj += 2) { @@ -1520,7 +1520,7 @@ void PoissonSolver::calcCoefficients2D(unsigned int from, unsigned int to template DataT PoissonSolver::getGridSizePhiInv() { - return MGParameters::normalizeGridToOneSector ? (INVTWOPI * SECTORSPERSIDE) : INVTWOPI; + return INVTWOPI * SECTORSPERSIDE / MGParameters::normalizeGridToNSector; } template class o2::tpc::PoissonSolver; diff --git a/Detectors/TPC/spacecharge/src/SpaceCharge.cxx b/Detectors/TPC/spacecharge/src/SpaceCharge.cxx index 07101bac15c23..b80d2a7606ee7 100644 --- a/Detectors/TPC/spacecharge/src/SpaceCharge.cxx +++ b/Detectors/TPC/spacecharge/src/SpaceCharge.cxx @@ -25,7 +25,7 @@ #include "Field/MagneticField.h" #include "CommonUtils/TreeStreamRedirector.h" #include "TPCBase/CalDet.h" -#include "TPCBase/Painter.h" +#include "TPCBaseRecSim/Painter.h" #include "MathUtils/Utils.h" #include "DataFormatsParameters/GRPMagField.h" #include "GPUDebugStreamer.h" @@ -43,6 +43,7 @@ #include "TStopwatch.h" #include "ROOT/RDataFrame.hxx" #include "THnSparse.h" +#include "TRandom.h" #include @@ -246,6 +247,10 @@ void SpaceCharge::setDefaultStaticDistortionsGEMFrameChargeUp(const Side template size_t SpaceCharge::getPhiBinsGapFrame(const Side side) const { + if (MGParameters::normalizeGridToNSector == 1) { + return 0; + } + const auto& regInf = Mapper::instance().getPadRegionInfo(0); const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth(); const auto globalPosGap = Mapper::LocalToGlobal(LocalPosition2D(regInf.getRadiusFirstRow(), -(localYEdgeIROC + GEMFrameParameters::WIDTHFRAME)), Sector(0)); @@ -268,16 +273,15 @@ void SpaceCharge::setPotentialBoundaryGEMFrameAlongR(const std::function< } template -std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const +std::vector> SpaceCharge::getPotentialBoundaryGEMFrameAlongRIndices(const Side side) const { - const bool simOneSectorOnly = MGParameters::normalizeGridToOneSector; const auto radiusStart = std::sqrt(std::pow(GEMFrameParameters::LENGTHFRAMEIROCBOTTOM / 2, 2) + std::pow(GEMFrameParameters::POSBOTTOM[0], 2)); const auto rStart = getNearestRVertex(radiusStart, side); const auto radiusEnd = std::sqrt(std::pow(GEMFrameParameters::LENGTHFRAMEOROC3TOP / 2, 2) + std::pow(GEMFrameParameters::POSTOP[3], 2)); const auto rEnd = getNearestRVertex(radiusEnd, side); // mParamGrid.NRVertices - 1 - const int verticesPerSector = simOneSectorOnly ? mParamGrid.NPhiVertices : mParamGrid.NPhiVertices / SECTORSPERSIDE; + const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector; const auto& regInf = Mapper::instance().getPadRegionInfo(0); const float localYEdgeIROC = regInf.getPadsInRowRegion(0) / 2 * regInf.getPadWidth(); @@ -300,7 +304,8 @@ std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongRIndice radii.emplace_back(std::sqrt(std::pow(GEMFrameParameters::POSTOP[stack], 2) + std::pow(localYEdge, 2))); } - std::vector potentialInd; + std::vector> potentialInd; + const float weight = 1; for (size_t iR = rStart; iR < rEnd; ++iR) { const DataT radius = getRVertex(iR, side); auto const it = std::lower_bound(radii.begin(), radii.end(), radius); @@ -315,13 +320,13 @@ std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongRIndice break; } - for (int sector = 0; sector < (simOneSectorOnly ? 1 : SECTORSPERSIDE); ++sector) { + for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) { const size_t iPhiLeft = sector * verticesPerSector + iPhiTmp; const size_t iZ = mParamGrid.NZVertices - 1; - potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft)); + potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight); if (iPhiTmp > 0) { const size_t iPhiRight = (sector + 1) * verticesPerSector - iPhiTmp; - potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight)); + potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight); } } } @@ -339,37 +344,35 @@ void SpaceCharge::setPotentialBoundaryGEMFrameAlongPhi(const std::functio } template -void SpaceCharge::setBoundaryFromIndices(const std::function& potentialFunc, const std::vector& indices, const Side side) +void SpaceCharge::setBoundaryFromIndices(const std::function& potentialFunc, const std::vector>& indices, const Side side) { - for (const auto& index : indices) { + /* + make check for the weights + Loop over bins in the radial direction + Check for duplicates and use the one with larger weight + */ + + for (const auto& indexw : indices) { + const int index = indexw.first; const int iZ = mPotential[side].getIndexZ(index); const int iR = mPotential[side].getIndexR(index); const int iPhi = mPotential[side].getIndexPhi(index); const DataT radius = getRVertex(iR, side); - mPotential[side](iZ, iR, iPhi) = potentialFunc(radius); + const float weight = indexw.second; + const float pot = mPotential[side](iZ, iR, iPhi); + const float potNew = weight * potentialFunc(radius); + if (std::abs(potNew) > std::abs(pot)) { + mPotential[side](iZ, iR, iPhi) = potNew; + } } } template -std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap) const +std::vector> SpaceCharge::getPotentialBoundaryGEMFrameAlongPhiIndices(const GEMstack stack, const bool bottom, const Side side, const bool outerFrame, const bool noGap) const { - const bool simOneSectorOnly = MGParameters::normalizeGridToOneSector; - // to avoid double counting auto indices = getPotentialBoundaryGEMFrameAlongRIndices(side); - if (!bottom && outerFrame) { - // if OROC3 to OFC check outer GEM frame from OROC3! - const auto indicesOROC3 = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::OROC3gem, false, side, false); - indices.insert(indices.end(), indicesOROC3.begin(), indicesOROC3.end()); - std::sort(indices.begin(), indices.end()); - } else if (bottom && outerFrame) { - // if IROC to IFC check inner GEM frame from IROC - const auto indicesIROC = getPotentialBoundaryGEMFrameAlongPhiIndices(GEMstack::IROCgem, true, side, false); - indices.insert(indices.end(), indicesIROC.begin(), indicesIROC.end()); - std::sort(indices.begin(), indices.end()); - } - int region = 0; float offsStart = 0; float offsEnd = 0; @@ -415,10 +418,10 @@ std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongPhiIndi nVerticesR = 1; } - std::vector potentialInd; - const int verticesPerSector = simOneSectorOnly ? mParamGrid.NPhiVertices : mParamGrid.NPhiVertices / SECTORSPERSIDE; - const auto nBinsPhi = (outerFrame || noGap) ? 0 : (simOneSectorOnly ? 0 : getPhiBinsGapFrame(side)); - for (int sector = 0; sector < (simOneSectorOnly ? 1 : SECTORSPERSIDE); ++sector) { + std::vector> potentialInd; // index, weight + const int verticesPerSector = mParamGrid.NPhiVertices / MGParameters::normalizeGridToNSector; + const auto nBinsPhi = (outerFrame || noGap) ? 0 : getPhiBinsGapFrame(side); + for (int sector = 0; sector < MGParameters::normalizeGridToNSector; ++sector) { const auto offsetPhi = sector * verticesPerSector + verticesPerSector / 2; for (size_t iPhiLocal = 0; iPhiLocal <= (verticesPerSector / 2 - nBinsPhi); ++iPhiLocal) { const auto iPhiLeft = offsetPhi + iPhiLocal; @@ -432,31 +435,62 @@ std::vector SpaceCharge::getPotentialBoundaryGEMFrameAlongPhiIndi // end at gem frame if ((outerFrame && (stack == GEMstack::IROCgem))) { - nREnd = (radiusBottom - getRVertex(1, side)) / getGridSpacingR(side) + 2; // 2 safety margin + // TODO: remove this? + const float marginCM = 0; // 0.4; + const int nMargingBins = marginCM / getGridSpacingR(side) + 0.5; + nREnd = (radiusBottom - getRVertex(1, side) + 0.5) / getGridSpacingR(side) + nMargingBins; // 2 safety margin + radiusMax = 3 + getRVertex(getNearestRVertex(radiusBottom + getGridSpacingR(side) * nMargingBins, side), side); } - if (rStart == 0) { + rStart -= 1; + nREnd += 1; + if (rStart <= 0) { rStart = 1; } + if (nREnd >= mParamGrid.NRVertices) { + nREnd = mParamGrid.NRVertices - 1; + } + + float lxMin = radiusStart; + if ((outerFrame && (stack == GEMstack::IROCgem))) { + lxMin = 0; + } + const float lxMax = (nREnd == mParamGrid.NRVertices - 1) ? 9999 : radiusMax; for (size_t iR = rStart; iR < nREnd; ++iR) { const size_t iZ = mParamGrid.NZVertices - 1; + float weight = 1; if (iPhiLeft < getNPhiVertices()) { - if (noGap || !std::binary_search(indices.begin(), indices.end(), mPotential[side].getDataIndex(iZ, iR, iPhiLeft))) { - potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft)); + if (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); })) { + + // check how much of the bin is in the lx range and assign weigth + const int nIterPoints = 1000; + int nPointsGood = 0; + for (int i = 0; i < nIterPoints; ++i) { + const float radius = getRVertex(iR, side) + getGridSpacingR(side) * gRandom->Uniform(-0.5, 0.5); + const float phi = getGridSpacingPhi(side) * gRandom->Uniform(-0.5, 0.5); + const DataT lx = radius * std::cos(phi + localphi); + if ((lx >= lxMin) && (lx <= lxMax)) { + ++nPointsGood; + } + } + weight = nPointsGood / double(nIterPoints); + potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiLeft), weight); } } - if (iPhiLocal && (noGap || !std::binary_search(indices.begin(), indices.end(), mPotential[side].getDataIndex(iZ, iR, iPhiRight)))) { - potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight)); + if (iPhiLocal && (noGap || !std::binary_search(indices.begin(), indices.end(), std::make_pair(mPotential[side].getDataIndex(iZ, iR, iPhiRight), 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); }))) { + potentialInd.emplace_back(mPotential[side].getDataIndex(iZ, iR, iPhiRight), weight); } } } } // remove duplicate entries - std::unordered_set set(potentialInd.begin(), potentialInd.end()); - potentialInd.assign(set.begin(), set.end()); std::sort(potentialInd.begin(), potentialInd.end()); + + // Remove duplicates + potentialInd.erase(std::unique(potentialInd.begin(), potentialInd.end()), potentialInd.end()); + return potentialInd; } @@ -1809,6 +1843,18 @@ DataT SpaceCharge::getDensityCyl(const DataT z, const DataT r, const Data return mInterpolatorDensity[side](z, r, phi); } +template +std::vector SpaceCharge::getDensityCyl(const std::vector& z, const std::vector& r, const std::vector& phi, const Side side) const +{ + const auto nPoints = z.size(); + std::vector density(nPoints); +#pragma omp parallel for num_threads(sNThreads) + for (size_t i = 0; i < nPoints; ++i) { + density[i] = getDensityCyl(z[i], r[i], phi[i], side); + } + return density; +} + template DataT SpaceCharge::getPotentialCyl(const DataT z, const DataT r, const DataT phi, const Side side) const { @@ -1885,7 +1931,8 @@ void SpaceCharge::getCorrections(const DataT x, const DataT y, const Data } else { // convert cartesian to polar const DataT radius = getRadiusFromCartesian(x, y); - const DataT phi = getPhiFromCartesian(x, y); + DataT phi = getPhiFromCartesian(x, y); + o2::math_utils::detail::bringTo02PiGen(phi); DataT corrR{}; DataT corrRPhi{}; @@ -2421,7 +2468,7 @@ void SpaceCharge::makeElectronDriftPathGif(const char* inpFile, TH2F& hDu template void SpaceCharge::dumpToTree(const char* outFileName, const Side side, const int nZPoints, const int nRPoints, const int nPhiPoints, const bool randomize) const { - const DataT phiSpacing = GridProp::getGridSpacingPhi(nPhiPoints) / (MGParameters::normalizeGridToOneSector ? SECTORSPERSIDE : 1); + const DataT phiSpacing = GridProp::getGridSpacingPhi(nPhiPoints) * (MGParameters::normalizeGridToNSector / double(SECTORSPERSIDE)); const DataT rSpacing = GridProp::getGridSpacingR(nRPoints); const DataT zSpacing = side == Side::A ? GridProp::getGridSpacingZ(nZPoints) : -GridProp::getGridSpacingZ(nZPoints); @@ -2459,6 +2506,7 @@ void SpaceCharge::dumpToTree(const char* outFileName, const Side side, co std::vector> lPosOut(nPhiPoints); std::vector> sectorOut(nPhiPoints); std::vector> globalIdxOut(nPhiPoints); + std::vector> isOnPadPlane(nPhiPoints); #pragma omp parallel for num_threads(sNThreads) for (int iPhi = 0; iPhi < nPhiPoints; ++iPhi) { @@ -2494,6 +2542,7 @@ void SpaceCharge::dumpToTree(const char* outFileName, const Side side, co lPosOut[iPhi].reserve(nPoints); sectorOut[iPhi].reserve(nPoints); globalIdxOut[iPhi].reserve(nPoints); + isOnPadPlane[iPhi].reserve(nPoints); std::mt19937 rng(std::random_device{}()); DataT phiPos = iPhi * phiSpacing; @@ -2603,6 +2652,12 @@ void SpaceCharge::dumpToTree(const char* outFileName, const Side side, co sectorOut[iPhi].emplace_back(sector); const size_t idx = (iZ + nZPoints * (iR + iPhi * nRPoints)); globalIdxOut[iPhi].emplace_back(idx); + + const float xDist = getXFromPolar(radiusDistorted, phiDistorted); + const float yDist = getYFromPolar(radiusDistorted, phiDistorted); + GlobalPosition3D posTmp(xDist, yDist, zPos); + const DigitPos digiPadPos = o2::tpc::Mapper::instance().findDigitPosFromGlobalPosition(posTmp); + isOnPadPlane[iPhi].emplace_back(digiPadPos.isValid()); } } } @@ -2645,6 +2700,7 @@ void SpaceCharge::dumpToTree(const char* outFileName, const Side side, co dfStore = dfStore.DefineSlotEntry("bZ", [&bZOut = bZOut](unsigned int, ULong64_t entry) { return bZOut[entry]; }); dfStore = dfStore.DefineSlotEntry("bPhi", [&bPhiOut = bPhiOut](unsigned int, ULong64_t entry) { return bPhiOut[entry]; }); dfStore = dfStore.DefineSlotEntry("globalIndex", [&globalIdxOut = globalIdxOut](unsigned int, ULong64_t entry) { return globalIdxOut[entry]; }); + dfStore = dfStore.DefineSlotEntry("isOnPadPlane", [&isOnPadPlane = isOnPadPlane](unsigned int, ULong64_t entry) { return isOnPadPlane[entry]; }); dfStore.Snapshot("tree", outFileName); timer.Print("u"); } @@ -3356,20 +3412,20 @@ void SpaceCharge::readMetaData(std::string_view file) } template -void SpaceCharge::setSimOneSector() +void SpaceCharge::setSimNSector(const int nSectors) { LOGP(warning, "Use this feature only if you know what you are doing!"); - o2::tpc::MGParameters::normalizeGridToOneSector = true; - RegularGrid gridTmp[FNSIDES]{{GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::A) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE, mParamGrid}, - {GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::C) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE, mParamGrid}}; + o2::tpc::MGParameters::normalizeGridToNSector = nSectors; + RegularGrid gridTmp[FNSIDES]{{GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::A) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid}, + {GridProp::ZMIN, GridProp::RMIN, GridProp::PHIMIN, getSign(Side::C) * GridProp::getGridSpacingZ(mParamGrid.NZVertices), GridProp::getGridSpacingR(mParamGrid.NRVertices), GridProp::getGridSpacingPhi(mParamGrid.NPhiVertices) / SECTORSPERSIDE * nSectors, mParamGrid}}; mGrid3D[0] = gridTmp[0]; mGrid3D[1] = gridTmp[1]; } template -void SpaceCharge::unsetSimOneSector() +void SpaceCharge::unsetSimNSector() { - o2::tpc::MGParameters::normalizeGridToOneSector = false; + o2::tpc::MGParameters::normalizeGridToNSector = SECTORSPERSIDE; } template @@ -3424,6 +3480,20 @@ void SpaceCharge::addChargeDensity(const SpaceCharge& otherSC) mDensity[Side::C] += otherSC.mDensity[Side::C]; } +template +void SpaceCharge::addGlobalCorrections(const SpaceCharge& otherSC, const Side side) +{ + const bool sameGrid = (getNPhiVertices() == otherSC.getNPhiVertices()) && (getNRVertices() == otherSC.getNRVertices()) && (getNZVertices() == otherSC.getNZVertices()); + if (!sameGrid) { + LOGP(warning, "Space charge objects have different grid definition"); + return; + } + + mGlobalCorrdR[side] += otherSC.mGlobalCorrdR[side]; + mGlobalCorrdZ[side] += otherSC.mGlobalCorrdZ[side]; + mGlobalCorrdRPhi[side] += otherSC.mGlobalCorrdRPhi[side]; +} + template void SpaceCharge::fillChargeDensityFromHisto(const char* file, const char* nameA, const char* nameC) { @@ -3739,9 +3809,27 @@ void SpaceCharge::setIFCChargeUpFallingPot(const float deltaPot, const fl template void SpaceCharge::setGlobalCorrections(const std::function& gCorr, const Side side) { - initContainer(mGlobalCorrdR[side], true); - initContainer(mGlobalCorrdZ[side], true); - initContainer(mGlobalCorrdRPhi[side], true); + setGlobalDistCorr(Type::Corrections, gCorr, side); +} + +template +void SpaceCharge::setGlobalDistortions(const std::function& gDist, const Side side) +{ + setGlobalDistCorr(Type::Distortions, gDist, side); +} + +template +void SpaceCharge::setGlobalDistCorr(const Type type, const std::function& gFunc, const Side side) +{ + if (type == Type::Distortions) { + initContainer(mGlobalDistdR[side], true); + initContainer(mGlobalDistdZ[side], true); + initContainer(mGlobalDistdRPhi[side], true); + } else { + initContainer(mGlobalCorrdR[side], true); + initContainer(mGlobalCorrdZ[side], true); + initContainer(mGlobalCorrdRPhi[side], true); + } #pragma omp parallel for num_threads(sNThreads) for (unsigned int iPhi = 0; iPhi < mParamGrid.NPhiVertices; ++iPhi) { @@ -3761,7 +3849,7 @@ void SpaceCharge::setGlobalCorrections(const std::function::setGlobalCorrections(const std::function::setROCMisalignment(int stackType, int misalignmentType, } template -void SpaceCharge::fillROCMisalignment(const std::vector& indicesTop, const std::vector& indicesBottom, int sector, int misalignmentType, const std::pair& deltaPotPar) +void SpaceCharge::fillROCMisalignment(const std::vector>& indicesTop, const std::vector>& indicesBottom, int sector, int misalignmentType, const std::pair& deltaPotPar) { - for (const auto& index : indicesTop) { + for (const auto& indexw : indicesTop) { + const int index = indexw.first; const int iZ = DataContainer3D::getIndexZ(index, getNZVertices(), getNRVertices(), getNPhiVertices()); const int iRStart = DataContainer3D::getIndexR(index, getNZVertices(), getNRVertices(), getNPhiVertices()); const int iPhi = DataContainer3D::getIndexPhi(index, getNZVertices(), getNRVertices(), getNPhiVertices()); @@ -3853,7 +3948,8 @@ void SpaceCharge::fillROCMisalignment(const std::vector& indicesT for (size_t iR = iRStart; iR > 0; --iR) { const size_t currInd = (iZ + getNZVertices() * (iR + iPhi * getNRVertices())); - const bool foundVertexBottom = std::binary_search(indicesBottom.begin(), indicesBottom.end(), currInd); + const bool foundVertexBottom = std::binary_search(indicesBottom.begin(), indicesBottom.end(), std::make_pair(currInd, 0.0f), [](const auto& a, const auto& b) { return (a.first < b.first); }); + if (foundVertexBottom) { break; } @@ -3982,7 +4078,7 @@ void SpaceCharge::initAfterReadingFromFile() } template -float SpaceCharge::getDCAr(float tgl, const int nPoints, const float phi, o2::utils::TreeStreamRedirector* pcstream) const +float SpaceCharge::getDCAr(float tgl, const int nPoints, const float phi, float rStart, o2::utils::TreeStreamRedirector* pcstream) const { const float rmin = getRMin(o2::tpc::Side::A); std::vector dRphi; @@ -3990,7 +4086,7 @@ float SpaceCharge::getDCAr(float tgl, const int nPoints, const float phi, dRphi.reserve(nPoints); r.reserve(nPoints); for (int i = 0; i < nPoints; ++i) { - float radius = rmin + i; + float radius = (rStart > 0) ? (rStart + i) : (rmin + i); float z = tgl * radius; DataT distZ = 0; DataT distR = 0; @@ -4030,6 +4126,7 @@ float SpaceCharge::getDCAr(float tgl, const int nPoints, const float phi, << "r=" << r << "dRphi=" << dRphi << "tgl=" << tgl + << "phi=" << phi << "dca=" << dca << "rInterpol=" << rInterpol << "dRPhiInterpol=" << dRPhiInterpol @@ -4047,6 +4144,26 @@ void SpaceCharge::setPotential(int iz, int ir, int iphi, Side side, float mPotential[side](iz, ir, iphi) = val; } +template +void SpaceCharge::downSampleObject(const int nZNew, const int nRNew, const int nPhiNew) +{ + o2::tpc::SpaceCharge scNew(getBField(), nZNew, nRNew, nPhiNew); + for (int iside = 0; iside < 2; ++iside) { + const o2::tpc::Side side = (iside == 0) ? o2::tpc::Side::A : o2::tpc::Side::C; + const std::vector> dataRef{mLocalDistdR[iside], mLocalDistdZ[iside], mLocalDistdRPhi[iside], mLocalVecDistdR[iside], mLocalVecDistdZ[iside], mLocalVecDistdRPhi[iside], mLocalCorrdR[iside], mLocalCorrdZ[iside], mLocalCorrdRPhi[iside], mGlobalDistdR[iside], mGlobalDistdZ[iside], mGlobalDistdRPhi[iside], mGlobalCorrdR[iside], mGlobalCorrdZ[iside], mGlobalCorrdRPhi[iside], mDensity[iside], mPotential[iside], mElectricFieldEr[iside], mElectricFieldEz[iside], mElectricFieldEphi[iside]}; + const std::vector> dataNew{scNew.mLocalDistdR[iside], scNew.mLocalDistdZ[iside], scNew.mLocalDistdRPhi[iside], scNew.mLocalVecDistdR[iside], scNew.mLocalVecDistdZ[iside], scNew.mLocalVecDistdRPhi[iside], scNew.mLocalCorrdR[iside], scNew.mLocalCorrdZ[iside], scNew.mLocalCorrdRPhi[iside], scNew.mGlobalDistdR[iside], scNew.mGlobalDistdZ[iside], scNew.mGlobalDistdRPhi[iside], scNew.mGlobalCorrdR[iside], scNew.mGlobalCorrdZ[iside], scNew.mGlobalCorrdRPhi[iside], scNew.mDensity[iside], scNew.mPotential[iside], scNew.mElectricFieldEr[iside], scNew.mElectricFieldEz[iside], scNew.mElectricFieldEphi[iside]}; + for (int i = 0; i < dataRef.size(); ++i) { + const auto& objRef = dataRef[i].get(); + if (objRef.getNDataPoints()) { + auto& objNew = dataNew[i].get(); + scNew.initContainer(objNew, true); + objNew = objRef.convert(scNew.mGrid3D[iside], mGrid3D[iside], sNThreads); + } + } + } + *this = std::move(scNew); +} + using DataTD = double; template class o2::tpc::SpaceCharge; diff --git a/Detectors/TPC/workflow/CMakeLists.txt b/Detectors/TPC/workflow/CMakeLists.txt index 48ebb54ac4070..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 @@ -200,7 +201,7 @@ o2_add_executable(idc-test-ft o2_add_executable(miptrack-filter COMPONENT_NAME tpc SOURCES src/tpc-miptrack-filter.cxx - PUBLIC_LINK_LIBRARIES O2::TPCWorkflow) + PUBLIC_LINK_LIBRARIES O2::TPCWorkflow O2::GlobalTrackingWorkflow) o2_add_executable(track-and-cluster-filter COMPONENT_NAME tpc @@ -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/CalibratorPadGainTracksSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/CalibratorPadGainTracksSpec.h index f9d5501196eb7..3ccef73a4a8fc 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/CalibratorPadGainTracksSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/CalibratorPadGainTracksSpec.h @@ -22,7 +22,7 @@ #include "CommonUtils/NameConf.h" #include "Framework/Task.h" #include "Framework/ConfigParamRegistry.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCWorkflow/ProcessingHelpers.h" #include "DetectorsBase/GRPGeomHelper.h" #include "DetectorsCalibration/Utils.h" diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/EntropyDecoderSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/EntropyDecoderSpec.h index d36391adfab51..767b68644d698 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/EntropyDecoderSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace tpc class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none") : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -47,7 +47,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt = "none"); } // namespace tpc } // namespace o2 diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/EntropyEncoderSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/EntropyEncoderSpec.h index ac6fafec0a554..1b8483953a8ab 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/EntropyEncoderSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/EntropyEncoderSpec.h @@ -45,7 +45,7 @@ class VDriftHelper; class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool fromFile, bool selIR = false, std::shared_ptr pgg = std::shared_ptr()); + EntropyEncoderSpec(bool fromFile, bool selIR = false, std::shared_ptr pgg = std::shared_ptr(), const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -71,7 +71,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR = false, const std::string& ctfdictOpt = "none"); } // end namespace tpc } // end namespace o2 diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/MIPTrackFilterSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/MIPTrackFilterSpec.h index 05024baad37b3..45406e6c01bbd 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/MIPTrackFilterSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/MIPTrackFilterSpec.h @@ -17,6 +17,8 @@ #define O2_TPC_MIPTRACKFILTERSPEC_H_ #include "Framework/DataProcessorSpec.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +using GID = o2::dataformats::GlobalTrackID; using namespace o2::framework; @@ -24,7 +26,7 @@ namespace o2::tpc { /// create a processor spec -o2::framework::DataProcessorSpec getMIPTrackFilterSpec(); +o2::framework::DataProcessorSpec getMIPTrackFilterSpec(GID::mask_t srcTracks = GID::getSourcesMask("TPC")); } // namespace o2::tpc diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h b/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h index f86afc310b04c..3526e9622b83c 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/RecoWorkflow.h @@ -30,20 +30,20 @@ struct InputSpec; } namespace tpc { -struct CorrectionMapsLoaderGloOpts; +struct CorrectionMapsGloOpts; namespace reco_workflow { /// define input and output types of the workflow -enum struct InputType { PassThrough, // No processing, just pass through available inputs to the writers, defined by the OutputType - Digitizer, // directly read digits from channel {TPC:DIGITS} - Digits, // read digits from file - ClustersHardware, // read hardware clusters in raw page format from file - Clusters, // read native clusters from file - CompClusters, // read compressed cluster container - CompClustersCTF, // compressed clusters from CTF, as flat format - CompClustersFlat, // compressed clusters in flat format, used as input for the entropy encoder - EncodedClusters, // read encoded clusters +enum struct InputType { PassThrough, // No processing, just pass through available inputs to the writers, defined by the OutputType + Digitizer, // directly read digits from channel {TPC:DIGITS} + Digits, // read digits from file + ClustersHardware, // read hardware clusters in raw page format from file + Clusters, // read native clusters from file + CompClustersRoot, // read compressed cluster in ROOT format + CompClustersFlat, // compressed clusters from flat format (e.g. from CTF) + CompClustersFlatForEncode, // compressed clusters in flat format, used as input for the entropy encoder, no gpu-reco + EncodedClusters, // read encoded clusters ZSRaw, }; @@ -59,7 +59,8 @@ enum struct OutputType { Digits, ClustersHardware, Clusters, Tracks, - CompClusters, + CompClustersRoot, + CompClustersFlat, EncodedClusters, DisableWriter, SendClustersPerSector, @@ -72,18 +73,19 @@ 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", bool selIR = false, bool filteredInp = false, int deadMapSources = -1, 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 c5af27da7b8f7..34f29f94dff4d 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadGainTracksSpec.h @@ -25,9 +25,8 @@ #include "DataFormatsParameters/GRPObject.h" #include "TPCWorkflow/ProcessingHelpers.h" #include "Framework/CCDBParamSpec.h" -#include "TPCBase/CDBInterface.h" -#include "TPCCalibration/VDriftHelper.h" -#include "TPCCalibration/CorrectionMapsLoader.h" +#include "TPCBaseRecSim/CDBInterface.h" +#include "TPCFastTransformPOD.h" #include "DetectorsBase/GRPGeomHelper.h" #include "GPUO2InterfaceUtils.h" #include "DataFormatsGlobalTracking/RecoContainer.h" @@ -45,21 +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); } 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"); @@ -150,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); @@ -187,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; @@ -223,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) { @@ -246,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; @@ -269,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)"}}, @@ -292,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 @@ -309,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/TPCCalibPadRawSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadRawSpec.h index 19cbeb05f7007..7579e334ff267 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadRawSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCCalibPadRawSpec.h @@ -34,7 +34,7 @@ #include "DetectorsBase/TFIDInfoHelper.h" #include "DataFormatsTPC/TPCSectorHeader.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCCalibration/CalibPedestal.h" #include "TPCCalibration/CalibPulser.h" #include "TPCReconstruction/RawReaderCRU.h" 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/TPCFLPIDCSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPIDCSpec.h index ec3e158590661..e4b85ad7c04d9 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPIDCSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFLPIDCSpec.h @@ -31,7 +31,7 @@ #include "TPCCalibration/IDCFactorization.h" #include "Framework/CCDBParamSpec.h" #include "TPCWorkflow/ProcessingHelpers.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" using namespace o2::framework; using o2::header::gDataOriginTPC; diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeIDCSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeIDCSpec.h index 667386e6481ca..c8384cf9c9264 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeIDCSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeIDCSpec.h @@ -35,7 +35,7 @@ #include "TPCBase/CRU.h" #include "CommonUtils/NameConf.h" #include "TPCWorkflow/ProcessingHelpers.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "DetectorsCalibration/Utils.h" #include "TPCCalibration/IDCCCDBHelper.h" diff --git a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeSACSpec.h b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeSACSpec.h index f191f5f44761b..1757f3e223e86 100644 --- a/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeSACSpec.h +++ b/Detectors/TPC/workflow/include/TPCWorkflow/TPCFactorizeSACSpec.h @@ -29,7 +29,7 @@ #include "CCDB/CcdbApi.h" #include "TPCWorkflow/TPCDistributeSACSpec.h" #include "TPCWorkflow/ProcessingHelpers.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "DetectorsCalibration/Utils.h" #include "Framework/InputRecordWalker.h" 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/CalDetMergerPublisherSpec.cxx b/Detectors/TPC/workflow/src/CalDetMergerPublisherSpec.cxx index a504ffa606b84..bb3c927e3df4d 100644 --- a/Detectors/TPC/workflow/src/CalDetMergerPublisherSpec.cxx +++ b/Detectors/TPC/workflow/src/CalDetMergerPublisherSpec.cxx @@ -36,7 +36,7 @@ #include "DetectorsCalibration/Utils.h" #include "CCDB/CcdbApi.h" #include "CCDB/CcdbObjectInfo.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/CalDet.h" #include "TPCBase/CRUCalibHelpers.h" #include "TPCWorkflow/CalibRawPartInfo.h" diff --git a/Detectors/TPC/workflow/src/CalibdEdxSpec.cxx b/Detectors/TPC/workflow/src/CalibdEdxSpec.cxx index 5cf412f227d78..15ea241a7b350 100644 --- a/Detectors/TPC/workflow/src/CalibdEdxSpec.cxx +++ b/Detectors/TPC/workflow/src/CalibdEdxSpec.cxx @@ -13,6 +13,7 @@ /// \brief Workflow for time based dE/dx calibration. /// \author Thiago Badaró +#include "GPUO2InterfaceConfiguration.inc" #include "TPCWorkflow/CalibdEdxSpec.h" // o2 includes @@ -26,10 +27,9 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" -#include "GPUO2InterfaceConfigurableParam.h" #include "TPCCalibration/CalibdEdx.h" #include "TPCWorkflow/ProcessingHelpers.h" -#include "TPCBase/CDBTypes.h" +#include "TPCBaseRecSim/CDBTypes.h" #include "TPCBase/Utils.h" #include "DetectorsBase/GRPGeomHelper.h" @@ -70,8 +70,9 @@ class CalibdEdxDevice : public Task mCalib->setElectronCut(fitThreshold, fitPasses, fitThresholdLowFactor); mCalib->setMaterialType(mMatType); - mCustomdEdxFileName = o2::gpu::GPUConfigurableParamGPUSettingsO2::Instance().dEdxCorrFile; - mDisableTimeGain = o2::gpu::GPUConfigurableParamGPUSettingsO2::Instance().dEdxDisableResidualGain; + const auto& gpuConfig = GPU_GET_CONFIG(GPUSettingsO2); + mCustomdEdxFileName = gpuConfig.dEdxCorrFile; + mDisableTimeGain = gpuConfig.dEdxDisableResidualGain; if (mDisableTimeGain) { LOGP(info, "TimeGain correction was disabled via GPU_global.dEdxDisableResidualGain=1"); diff --git a/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx b/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx index 4c2a26da38908..dea1d85899675 100644 --- a/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx +++ b/Detectors/TPC/workflow/src/CalibratordEdxSpec.cxx @@ -13,6 +13,7 @@ /// \brief Workflow for time based dE/dx calibration. /// \author Thiago Badaró +#include "GPUO2InterfaceConfiguration.inc" #include "TPCWorkflow/CalibratordEdxSpec.h" #include @@ -29,11 +30,10 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" -#include "GPUO2InterfaceConfigurableParam.h" #include "TPCCalibration/CalibratordEdx.h" #include "TPCWorkflow/ProcessingHelpers.h" #include "DetectorsBase/GRPGeomHelper.h" -#include "TPCBase/CDBTypes.h" +#include "TPCBaseRecSim/CDBTypes.h" #include "TPCBase/Utils.h" using namespace o2::framework; @@ -86,8 +86,9 @@ class CalibratordEdxDevice : public Task mCalibrator->setTrackDebug(trackDebug); mCalibrator->setMakeGaussianFits(makeGaussianFits); - mCustomdEdxFileName = o2::gpu::GPUConfigurableParamGPUSettingsO2::Instance().dEdxCorrFile; - mDisableTimeGain = o2::gpu::GPUConfigurableParamGPUSettingsO2::Instance().dEdxDisableResidualGain; + const auto& gpuConfig = GPU_GET_CONFIG(GPUSettingsO2); + mCustomdEdxFileName = gpuConfig.dEdxCorrFile; + mDisableTimeGain = gpuConfig.dEdxDisableResidualGain; if (mDisableTimeGain) { LOGP(info, "TimeGain correction was disabled via GPU_global.dEdxDisableResidualGain=1"); diff --git a/Detectors/TPC/workflow/src/EntropyDecoderSpec.cxx b/Detectors/TPC/workflow/src/EntropyDecoderSpec.cxx index 4ff3573918722..dd73d582553e6 100644 --- a/Detectors/TPC/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/TPC/workflow/src/EntropyDecoderSpec.cxx @@ -26,7 +26,6 @@ namespace o2 { namespace tpc { - void EntropyDecoderSpec::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) { if (mCTFCoder.finaliseCCDB(matcher, obj)) { @@ -66,11 +65,14 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("ctf_TPC", "TPC", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_TPC", "TPC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TPC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_TPC", "TPC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TPC/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ @@ -79,10 +81,8 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) Outputs{OutputSpec{{"output"}, "TPC", "COMPCLUSTERSFLAT", 0, Lifetime::Timeframe}, OutputSpec{{"trigger"}, "TPC", "TRIGGERWORDS", 0, Lifetime::Timeframe}, OutputSpec{{"ctfrep"}, "TPC", "CTFDECREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace tpc } // namespace o2 diff --git a/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx b/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx index 2efa7077be125..4de5665f1b9a0 100644 --- a/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/TPC/workflow/src/EntropyEncoderSpec.cxx @@ -38,10 +38,9 @@ namespace o2 { namespace tpc { - EntropyEncoderSpec::~EntropyEncoderSpec() = default; -EntropyEncoderSpec::EntropyEncoderSpec(bool fromFile, bool selIR, std::shared_ptr pgg) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mFromFile(fromFile), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool fromFile, bool selIR, std::shared_ptr pgg, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mFromFile(fromFile), mSelIR(selIR) { if (mSelIR) { mGRPRequest = pgg; @@ -159,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; @@ -206,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(); @@ -241,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)) { } @@ -254,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); @@ -305,13 +304,16 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR, const std::string& ctfdictOpt) { std::vector inputs; header::DataDescription inputType = inputFromFile ? header::DataDescription("COMPCLUSTERS") : header::DataDescription("COMPCLUSTERSFLAT"); inputs.emplace_back("input", "TPC", inputType, 0, Lifetime::Timeframe); inputs.emplace_back("trigger", "TPC", "TRIGGERWORDS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "TPC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TPC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "TPC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TPC/Calib/CTFDictionaryTree")); + } std::shared_ptr ggreq; if (selIR) { @@ -324,9 +326,8 @@ DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR) inputs, Outputs{{"TPC", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "TPC", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(inputFromFile, selIR, ggreq)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"no-ctf-columns-combining", VariantType::Bool, false, {"Do not combine correlated columns in CTF"}}, + AlgorithmSpec{adaptFromTask(inputFromFile, selIR, ggreq, ctfdictOpt)}, + Options{{"no-ctf-columns-combining", VariantType::Bool, false, {"Do not combine correlated columns in CTF"}}, {"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"}}, {"irframe-clusters-maxeta", VariantType::Float, 1.5f, {"Max eta for non-assigned clusters"}}, @@ -335,6 +336,5 @@ DataProcessorSpec getEntropyEncoderSpec(bool inputFromFile, bool selIR) {"nThreads-tpc-encoder", VariantType::UInt32, 1u, {"number of threads to use for decoding"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace tpc } // namespace o2 diff --git a/Detectors/TPC/workflow/src/IDCToVectorSpec.cxx b/Detectors/TPC/workflow/src/IDCToVectorSpec.cxx index 27dbcf5d85bbf..da8de5f262cdf 100644 --- a/Detectors/TPC/workflow/src/IDCToVectorSpec.cxx +++ b/Detectors/TPC/workflow/src/IDCToVectorSpec.cxx @@ -72,6 +72,7 @@ class IDCToVectorDevice : public o2::framework::Task 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(); @@ -405,7 +406,7 @@ class IDCToVectorDevice : public o2::framework::Task for (const auto& inf : infVec) { if (!inf.hasBothEPs()) { - LOGP(error, "IDC CRU {:3}: data missing at ({:8}, {:4}) for one or both end points {:02b} in TF {}", cru, inf.heartbeatOrbit, inf.heartbeatBC, inf.epSeen, tfCounter); + LOGP(warning, "IDC CRU {:3}: data missing at ({:8}, {:4}) for one or both end points {:02b} in TF {}", cru, inf.heartbeatOrbit, inf.heartbeatBC, inf.epSeen, tfCounter); hasErrors = true; } } @@ -422,7 +423,7 @@ class IDCToVectorDevice : public o2::framework::Task } if (!std::equal(infVecComp->begin(), infVecComp->end(), infVec.begin())) { - LOGP(error, "IDC CRU {:3}: mismatch in orbit numbers", cru); + LOGP(warning, "IDC CRU {:3}: mismatch in orbit numbers", cru); hasErrors = true; } } @@ -606,9 +607,10 @@ o2::framework::DataProcessorSpec getIDCToVectorSpec(const std::string inputSpec, {"write-raw-data-on-error", VariantType::Bool, false, {"dump raw data in case errors occurred"}}, {"raw-file-name", VariantType::String, "/tmp/idc_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"}}, + {"check-incomplete-hbf", VariantType::Bool, false, {"false: don't chck; true: check and report"}}, {"pedestal-url", VariantType::String, "ccdb-default", {"ccdb-default: load from NameConf::getCCDBServer() OR ccdb url (must contain 'ccdb' OR pedestal file name"}}, {"swap-links", VariantType::Bool, false, {"swap links to circumvent bug in FW"}}, } // end Options - }; // end DataProcessorSpec + }; // end DataProcessorSpec } } // namespace o2::tpc diff --git a/Detectors/TPC/workflow/src/MIPTrackFilterSpec.cxx b/Detectors/TPC/workflow/src/MIPTrackFilterSpec.cxx index 33b9039298264..eff1a694a4727 100644 --- a/Detectors/TPC/workflow/src/MIPTrackFilterSpec.cxx +++ b/Detectors/TPC/workflow/src/MIPTrackFilterSpec.cxx @@ -16,7 +16,6 @@ #include "TPCWorkflow/MIPTrackFilterSpec.h" #include -#include #include #include #include @@ -24,7 +23,7 @@ // o2 includes #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsTPC/TrackCuts.h" -#include "DetectorsCalibration/Utils.h" +#include "Framework/CCDBParamSpec.h" #include "Framework/Logger.h" #include "DetectorsBase/GRPGeomHelper.h" #include "Framework/Task.h" @@ -33,8 +32,14 @@ #include "Framework/ConfigParamRegistry.h" #include "TPCWorkflow/ProcessingHelpers.h" #include "Headers/DataHeader.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "ReconstructionDataFormats/PrimaryVertex.h" +#include "DataFormatsCalibration/MeanVertexObject.h" +#include "ReconstructionDataFormats/VtxTrackRef.h" using namespace o2::framework; +using DataRequest = o2::globaltracking::DataRequest; +using GID = o2::dataformats::GlobalTrackID; namespace o2::tpc { @@ -42,7 +47,8 @@ namespace o2::tpc class MIPTrackFilterDevice : public Task { public: - MIPTrackFilterDevice(std::shared_ptr gr) : mGRPGeomRequest(gr) {} + MIPTrackFilterDevice(std::shared_ptr gr, std::shared_ptr dr, GID::mask_t trackSourcesMask) + : mGRPGeomRequest(gr), mDataRequest(dr), mTrackSourcesMask(trackSourcesMask) {} void init(framework::InitContext& ic) final; void run(ProcessingContext& pc) final; @@ -53,16 +59,20 @@ class MIPTrackFilterDevice : public Task void sendOutput(DataAllocator& output); std::shared_ptr mGRPGeomRequest; - TrackCuts mCuts{}; ///< Tracks cuts object - std::vector mMIPTracks; ///< Filtered MIP tracks - unsigned int mProcessEveryNthTF{1}; ///< process every Nth TF only - int mMaxTracksPerTF{-1}; ///< max number of MIP tracks processed per TF - uint32_t mTFCounter{0}; ///< counter to keep track of the TFs - int mProcessNFirstTFs{0}; ///< number of first TFs which are not sampled - float mDCACut{-1}; ///< DCA cut - bool mSendDummy{false}; ///< send empty data in case TF is skipped - - bool acceptDCA(const TrackTPC& track); + std::shared_ptr mDataRequest; + GID::mask_t mTrackSourcesMask; + TrackCuts mCuts{}; ///< Tracks cuts object + std::vector mMIPTracks; ///< Filtered MIP tracks + o2::dataformats::MeanVertexObject mVtx; ///< Mean vertex object + unsigned int mProcessEveryNthTF{1}; ///< process every Nth TF only + int mMaxTracksPerTF{-1}; ///< max number of MIP tracks processed per TF + uint32_t mTFCounter{0}; ///< counter to keep track of the TFs + int mProcessNFirstTFs{0}; ///< number of first TFs which are not sampled + float mDCACut{-1}; ///< DCA cut + float mDCAZCut{-1}; ///< DCA z cut + bool mSendDummy{false}; ///< send empty data in case TF is skipped + + bool acceptDCA(o2::track::TrackPar propTrack, o2::math_utils::Point3D refPoint, bool useDCAz = false); }; void MIPTrackFilterDevice::init(framework::InitContext& ic) @@ -100,6 +110,7 @@ void MIPTrackFilterDevice::init(framework::InitContext& ic) mCuts.setCutLooper(cutLoopers); mDCACut = ic.options().get("dca-cut"); + mDCAZCut = ic.options().get("dca-z-cut"); o2::base::GRPGeomHelper::instance().setRequest(mGRPGeomRequest); } @@ -107,6 +118,8 @@ void MIPTrackFilterDevice::init(framework::InitContext& ic) void MIPTrackFilterDevice::run(ProcessingContext& pc) { o2::base::GRPGeomHelper::instance().checkUpdates(pc); + pc.inputs().get("meanvtx"); + const auto currentTF = processing_helpers::getCurrentTF(pc); if ((mTFCounter++ % mProcessEveryNthTF) && (currentTF >= mProcessNFirstTFs)) { LOGP(info, "Skipping TF {}", currentTF); @@ -117,19 +130,60 @@ void MIPTrackFilterDevice::run(ProcessingContext& pc) return; } - const auto tracks = pc.inputs().get>("tracks"); - const auto nTracks = tracks.size(); + o2::globaltracking::RecoContainer recoData; + recoData.collectData(pc, *mDataRequest); + const auto tracksTPC = recoData.getTPCTracks(); + const auto nTracks = tracksTPC.size(); + + // indices to good tracks + std::vector indices; + indices.reserve(nTracks); + + const auto useGlobalTracks = mTrackSourcesMask[GID::ITSTPC]; + o2::math_utils::Point3D vertex = mVtx.getXYZ(); + + if (useGlobalTracks) { + auto trackIndex = recoData.getPrimaryVertexMatchedTracks(); // Global ID's for associated tracks + auto vtxRefs = recoData.getPrimaryVertexMatchedTrackRefs(); // references from vertex to these track IDs + std::vector selSrc{GID::ITSTPC, GID::ITSTPCTRD, GID::ITSTPCTRDTOF}; // for Instance + // LOGP(info, "Number of vertex tracks: {}", vtxRefs.size()); + const auto nv = (vtxRefs.size() > 0) ? vtxRefs.size() - 1 : 0; // note: the last entry groups the tracks which were not related to any vertex, to skip them, use vtxRefs.size()-1 + + for (int iv = 0; iv < nv; iv++) { + const auto& vtref = vtxRefs[iv]; + // LOGP(info, "Processing vertex {} with {} tracks", iv, vtref.getEntries()); + vertex = recoData.getPrimaryVertex(iv).getXYZ(); + // LOGP(info, "Vertex position: x={} y={} z={}", vertex.x(), vertex.y(), vertex.z()); + + for (auto src : selSrc) { + int idMin = vtxRefs[iv].getFirstEntryOfSource(src), idMax = idMin + vtxRefs[iv].getEntriesOfSource(src); + // LOGP(info, "Source {}: idMin={} idMax={}", GID::getSourceName(src), idMin, idMax); + + for (int i = idMin; i < idMax; i++) { + auto vid = trackIndex[i]; + const auto& track = recoData.getTrackParam(vid); // this is a COPY of the track param which we will modify during DCA calculation + auto gidTPC = recoData.getTPCContributorGID(vid); + if (gidTPC.isSourceSet()) { + const auto idxTPC = gidTPC.getIndex(); + if (mCuts.goodTrack(tracksTPC[idxTPC]) && acceptDCA(tracksTPC[idxTPC], vertex, true)) { + indices.emplace_back(idxTPC); + } + } + } + } + } - if ((mMaxTracksPerTF != -1) && (nTracks > mMaxTracksPerTF)) { - // indices to good tracks - std::vector indices; - indices.reserve(nTracks); + } else { for (size_t i = 0; i < nTracks; ++i) { - if (mCuts.goodTrack(tracks[i]) && acceptDCA(tracks[i])) { + if (mCuts.goodTrack(tracksTPC[i]) && acceptDCA(tracksTPC[i], vertex)) { indices.emplace_back(i); } } + } + + size_t nTracksSel = indices.size(); + if ((mMaxTracksPerTF != -1) && (nTracksSel > mMaxTracksPerTF)) { // in case no good tracks have been found if (indices.empty()) { mMIPTracks.clear(); @@ -144,15 +198,14 @@ void MIPTrackFilterDevice::run(ProcessingContext& pc) std::shuffle(indices.begin(), indices.end(), rng); // copy good tracks - const int loopEnd = (mMaxTracksPerTF > indices.size()) ? indices.size() : mMaxTracksPerTF; - for (int i = 0; i < loopEnd; ++i) { - mMIPTracks.emplace_back(tracks[indices[i]]); - } - } else { - std::copy_if(tracks.begin(), tracks.end(), std::back_inserter(mMIPTracks), [this](const auto& track) { return mCuts.goodTrack(track) && acceptDCA(track); }); + nTracksSel = (mMaxTracksPerTF > indices.size()) ? indices.size() : mMaxTracksPerTF; + } + + for (int i = 0; i < nTracksSel; ++i) { + mMIPTracks.emplace_back(tracksTPC[indices[i]]); } - LOGP(info, "Filtered {} MIP tracks out of {} total tpc tracks", mMIPTracks.size(), tracks.size()); + LOGP(info, "Filtered {} / {} MIP tracks out of {} total tpc tracks, using {}", mMIPTracks.size(), indices.size(), tracksTPC.size(), useGlobalTracks ? "global tracks" : "TPC only tracks"); sendOutput(pc.outputs()); mMIPTracks.clear(); } @@ -162,6 +215,11 @@ void MIPTrackFilterDevice::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { return; } + if (matcher == ConcreteDataMatcher("GLO", "MEANVERTEX", 0)) { + LOG(info) << "Setting new MeanVertex: " << ((const o2::dataformats::MeanVertexObject*)obj)->asString(); + mVtx = *(const o2::dataformats::MeanVertexObject*)obj; + return; + } } void MIPTrackFilterDevice::sendOutput(DataAllocator& output) { output.snapshot(Output{header::gDataOriginTPC, "MIPS", 0}, mMIPTracks); } @@ -171,7 +229,7 @@ void MIPTrackFilterDevice::endOfStream(EndOfStreamContext& eos) LOG(info) << "Finalizig MIP Tracks filter"; } -bool MIPTrackFilterDevice::acceptDCA(const TrackTPC& track) +bool MIPTrackFilterDevice::acceptDCA(o2::track::TrackPar propTrack, o2::math_utils::Point3D refPoint, bool useDCAz) { if (mDCACut < 0) { return true; @@ -179,21 +237,21 @@ bool MIPTrackFilterDevice::acceptDCA(const TrackTPC& track) auto propagator = o2::base::Propagator::Instance(); std::array dca; - const o2::math_utils::Point3D refPoint{0, 0, 0}; - o2::track::TrackPar propTrack(track); const auto ok = propagator->propagateToDCABxByBz(refPoint, propTrack, 2., o2::base::Propagator::MatCorrType::USEMatCorrLUT, &dca); const auto dcar = std::abs(dca[0]); - return ok && (dcar < mDCACut); + return ok && (dcar < mDCACut) && (!useDCAz || (std::abs(dca[1]) < mDCAZCut)); } -DataProcessorSpec getMIPTrackFilterSpec() +DataProcessorSpec getMIPTrackFilterSpec(GID::mask_t srcTracks) { std::vector outputs; outputs.emplace_back(header::gDataOriginTPC, "MIPS", 0, Lifetime::Sporadic); - std::vector inputs; - inputs.emplace_back("tracks", "TPC", "TRACKS"); + const auto useMC = false; + auto dataRequest = std::make_shared(); + dataRequest->requestTracks(srcTracks, useMC); + dataRequest->requestPrimaryVertices(useMC); auto ggRequest = std::make_shared(false, // orbitResetTime true, // GRPECS=true @@ -201,14 +259,16 @@ DataProcessorSpec getMIPTrackFilterSpec() true, // GRPMagField true, // askMatLUT o2::base::GRPGeomRequest::Aligned, // geometry - inputs, + dataRequest->inputs, true); + dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, o2::framework::ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); + return DataProcessorSpec{ "tpc-miptrack-filter", - inputs, + dataRequest->inputs, outputs, - adaptFromTask(ggRequest), + adaptFromTask(ggRequest, dataRequest, srcTracks), Options{ {"min-momentum", VariantType::Double, 0.35, {"minimum momentum cut"}}, {"max-momentum", VariantType::Double, 0.55, {"maximum momentum cut"}}, @@ -220,7 +280,8 @@ DataProcessorSpec getMIPTrackFilterSpec() {"process-first-n-TFs", VariantType::Int, 1, {"Number of first TFs which are not sampled"}}, {"send-dummy-data", VariantType::Bool, false, {"Send empty data in case TF is skipped"}}, {"dont-cut-loopers", VariantType::Bool, false, {"Do not cut loopers by comparing zout-zin"}}, - {"dca-cut", VariantType::Float, 3.f, {"DCA cut in cm, < 0 to disable"}}, + {"dca-cut", VariantType::Float, 3.f, {"DCA cut in xy (cm), < 0 to disable cut in xy and z"}}, + {"dca-z-cut", VariantType::Float, 5.f, {"DCA cut in z (cm)"}}, }}; } diff --git a/Detectors/TPC/workflow/src/RecoWorkflow.cxx b/Detectors/TPC/workflow/src/RecoWorkflow.cxx index 132d85bfce790..355bd0cb290f7 100644 --- a/Detectors/TPC/workflow/src/RecoWorkflow.cxx +++ b/Detectors/TPC/workflow/src/RecoWorkflow.cxx @@ -80,16 +80,17 @@ const std::unordered_map InputMap{ {"clustershardware", InputType::ClustersHardware}, {"clusters", InputType::Clusters}, {"zsraw", InputType::ZSRaw}, - {"compressed-clusters", InputType::CompClusters}, - {"compressed-clusters-ctf", InputType::CompClustersCTF}, - {"compressed-clusters-flat", InputType::CompClustersFlat}}; + {"compressed-clusters-root", InputType::CompClustersRoot}, + {"compressed-clusters-flat", InputType::CompClustersFlat}, + {"compressed-clusters-flat-for-encode", InputType::CompClustersFlatForEncode}}; const std::unordered_map OutputMap{ {"digits", OutputType::Digits}, {"clustershardware", OutputType::ClustersHardware}, {"clusters", OutputType::Clusters}, {"tracks", OutputType::Tracks}, - {"compressed-clusters", OutputType::CompClusters}, + {"compressed-clusters-root", OutputType::CompClustersRoot}, + {"compressed-clusters-flat", OutputType::CompClustersFlat}, {"encoded-clusters", OutputType::EncodedClusters}, {"disable-writer", OutputType::DisableWriter}, {"send-clusters-per-sector", OutputType::SendClustersPerSector}, @@ -99,8 +100,8 @@ 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, - int caClusterer, int zsOnTheFly, bool askDISTSTF, bool selIR, bool filteredInp, int deadMapSources, bool useMCTimeGain) + 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; try { @@ -118,18 +119,23 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto return std::find(outputTypes.begin(), outputTypes.end(), type) != outputTypes.end(); }; - if (filteredInp && !(inputType == InputType::PassThrough && isEnabled(OutputType::Tracks) && isEnabled(OutputType::Clusters) && isEnabled(OutputType::SendClustersPerSector))) { - throw std::invalid_argument("filtered-input option must be provided only with pass-through input and clusters,tracks,send-clusters-per-sector output"); + if (filteredInp && !(inputType == InputType::PassThrough)) { + throw std::invalid_argument("filtered-input option must be provided only with pass-through input"); } - bool decompressTPC = inputType == InputType::CompClustersCTF || inputType == InputType::CompClusters; + bool decompressTPC = inputType == InputType::CompClustersFlat || inputType == InputType::CompClustersRoot; // Disable not applicable settings depending on TPC input, no need to disable manually if (decompressTPC && (isEnabled(OutputType::Clusters) || isEnabled(OutputType::Tracks))) { caClusterer = false; zsOnTheFly = false; propagateMC = false; } - if (inputType == InputType::ZSRaw || inputType == InputType::CompClustersFlat) { + if (inputType == InputType::CompClustersFlatForEncode || inputType == InputType::CompClustersRoot || inputType == InputType::CompClustersFlat) { + caClusterer = false; + zsOnTheFly = false; + propagateMC = false; + } + if (inputType == InputType::ZSRaw) { caClusterer = true; zsOnTheFly = false; propagateMC = false; @@ -195,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)); } @@ -219,13 +223,11 @@ 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)); } - } else if (inputType == InputType::CompClusters) { + } else if (inputType == InputType::CompClustersRoot) { // TODO: need to check if we want to store the MC labels alongside with compressed clusters // for the moment reading of labels is disabled (last parameter is false) // TODO: make a different publisher spec for only one output spec, for now using the @@ -248,8 +250,9 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto // output matrix // Note: the ClusterHardware format is probably a deprecated legacy format and also the // ClusterDecoderRawSpec - bool produceCompClusters = isEnabled(OutputType::CompClusters); - bool runGPUReco = (produceTracks || produceCompClusters || (isEnabled(OutputType::Clusters) && caClusterer) || inputType == InputType::CompClustersCTF) && inputType != InputType::CompClustersFlat; + bool produceCompClustersRoot = isEnabled(OutputType::CompClustersRoot); + bool produceCompClustersFlat = isEnabled(OutputType::CompClustersFlat); + bool runGPUReco = (produceTracks || produceCompClustersRoot || produceCompClustersFlat || (isEnabled(OutputType::Clusters) && caClusterer) || inputType == InputType::CompClustersFlat) && inputType != InputType::CompClustersFlatForEncode; bool runHWDecoder = !caClusterer && (runGPUReco || isEnabled(OutputType::Clusters)); bool runClusterer = !caClusterer && (runHWDecoder || isEnabled(OutputType::ClustersHardware)); bool zsDecoder = inputType == InputType::ZSRaw; @@ -454,18 +457,14 @@ 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.enableMShape = sclOpts.enableMShapeCorrection; - cfg.enableCTPLumi = sclOpts.requestCTPLumi; cfg.decompressTPC = decompressTPC; - cfg.decompressTPCFromROOT = decompressTPC && inputType == InputType::CompClusters; + cfg.decompressTPCFromROOT = decompressTPC && inputType == InputType::CompClustersRoot; cfg.caClusterer = caClusterer; cfg.zsDecoder = zsDecoder; cfg.zsOnTheFly = zsOnTheFly; cfg.outputTracks = produceTracks; - cfg.outputCompClusters = produceCompClusters; - cfg.outputCompClustersFlat = runClusterEncoder; + cfg.outputCompClustersRoot = produceCompClustersRoot; + cfg.outputCompClustersFlat = produceCompClustersFlat || runClusterEncoder; cfg.outputCAClusters = isEnabled(OutputType::Clusters) && (caClusterer || decompressTPC); cfg.outputQA = isEnabled(OutputType::QA); cfg.outputSharedClusterMap = (isEnabled(OutputType::Clusters) || inputType == InputType::Clusters) && isEnabled(OutputType::Tracks) && !isEnabled(OutputType::NoSharedClusterMap); @@ -499,7 +498,7 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto // // selected by output type 'encoded-clusters' if (runClusterEncoder) { - specs.emplace_back(o2::tpc::getEntropyEncoderSpec(!runGPUReco && inputType != InputType::CompClustersFlat, selIR)); + specs.emplace_back(o2::tpc::getEntropyEncoderSpec(!runGPUReco && inputType != InputType::CompClustersFlatForEncode, selIR, ctfdictOpt)); } ////////////////////////////////////////////////////////////////////////////////////////////// @@ -545,8 +544,8 @@ framework::WorkflowSpec getWorkflow(CompletionPolicyData* policyData, std::vecto // // a writer process for compressed clusters container // - // selected by output type 'compressed-clusters' - if (produceCompClusters && !isEnabled(OutputType::DisableWriter)) { + // selected by output type 'compressed-clusters-root' + if (produceCompClustersRoot && !isEnabled(OutputType::DisableWriter)) { // defining the track writer process using the generic RootTreeWriter and generator tool // // defaults diff --git a/Detectors/TPC/workflow/src/SACProcessorSpec.cxx b/Detectors/TPC/workflow/src/SACProcessorSpec.cxx index e69533a0bb6d3..1d09b9f0a4fbe 100644 --- a/Detectors/TPC/workflow/src/SACProcessorSpec.cxx +++ b/Detectors/TPC/workflow/src/SACProcessorSpec.cxx @@ -25,7 +25,7 @@ #include "CommonUtils/TreeStreamRedirector.h" #include "DetectorsBase/GRPGeomHelper.h" #include "Framework/CCDBParamSpec.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "DataFormatsTPC/RawDataTypes.h" #include "TPCBase/RDHUtils.h" diff --git a/Detectors/TPC/workflow/src/TPCMergeIntegrateClusterSpec.cxx b/Detectors/TPC/workflow/src/TPCMergeIntegrateClusterSpec.cxx index 01538aab5ad90..2fdf0d001f475 100644 --- a/Detectors/TPC/workflow/src/TPCMergeIntegrateClusterSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCMergeIntegrateClusterSpec.cxx @@ -25,7 +25,7 @@ #include "DetectorsCommonDataFormats/FileMetaData.h" #include "Framework/DataTakingContext.h" #include "TPCCalibration/IDCFactorization.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "TPCBase/CalDet.h" #include diff --git a/Detectors/TPC/workflow/src/TPCRefitter.cxx b/Detectors/TPC/workflow/src/TPCRefitter.cxx index b2e41c8e808da..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,12 +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); - } + 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; @@ -82,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.; @@ -100,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 @@ -156,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)); @@ -164,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) @@ -211,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) @@ -334,15 +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->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 + 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 6065079c05e96..1df192dd5ec00 100644 --- a/Detectors/TPC/workflow/src/TPCScalerSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCScalerSpec.cxx @@ -19,7 +19,7 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/CCDBParamSpec.h" #include "Framework/ConfigParamRegistry.h" -#include "TPCBase/CDBInterface.h" +#include "TPCBaseRecSim/CDBInterface.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TPCCalibration/TPCScaler.h" #include "TPCCalibration/TPCMShapeCorrection.h" @@ -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 5007019d52910..0c0ae72056318 100644 --- a/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx +++ b/Detectors/TPC/workflow/src/TPCTimeSeriesSpec.cxx @@ -210,14 +210,14 @@ class TPCTimeSeries : public Task indicesITSTPC[tracksITSTPC[i].getRefTPC().getIndex()] = {i, idxVtx}; } - std::vector> idxTPCTrackToTOFCluster; // store for each tpc track index the index to the TOF cluster + std::vector> idxTPCTrackToTOFCluster; // store for each tpc track index the index to the TOF cluster // get matches to TOF in case skimmed data is produced if (mUnbinnedWriter) { // getLTIntegralOut(), ///< L,TOF integral calculated during the propagation // getSignal() mSignal = 0.0; ///< TOF time in ps o2::track::TrackLTIntegral defLT; - idxTPCTrackToTOFCluster = std::vector>(tracksTPC.size(), {-1, -999, -999, defLT, 0, 0, 0}); + idxTPCTrackToTOFCluster = std::vector>(tracksTPC.size(), {-1, -999, -999, defLT, 0, 0, 0, 0}); const std::vector> tofMatches{recoData.getTPCTOFMatches(), recoData.getTPCTRDTOFMatches(), recoData.getITSTPCTOFMatches(), recoData.getITSTPCTRDTOFMatches()}; const auto& ft0rec = recoData.getFT0RecPoints(); @@ -289,7 +289,7 @@ class TPCTimeSeries : public Task mask |= o2::dataformats::MatchInfoTOF::QualityFlags::hasT0_1BCbefore; } - idxTPCTrackToTOFCluster[refTPC] = {tpctofmatch.getIdxTOFCl(), tpctofmatch.getDXatTOF(), tpctofmatch.getDZatTOF(), ltIntegral, signal, deltaT, mask}; + idxTPCTrackToTOFCluster[refTPC] = {tpctofmatch.getIdxTOFCl(), tpctofmatch.getDXatTOF(), tpctofmatch.getDZatTOF(), ltIntegral, signal, deltaT, mask, tpctofmatch.getChannel() % 8736}; } } } @@ -1122,7 +1122,7 @@ class TPCTimeSeries : public Task return isGoodTrack; } - void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters) + void fillDCA(const gsl::span tracksTPC, const gsl::span tracksITSTPC, const gsl::span vertices, const int iTrk, const int iThread, const std::unordered_map>& indicesITSTPC, const gsl::span tracksITS, const std::vector>& idxTPCTrackToTOFCluster, const gsl::span tofClusters) { const auto& trackFull = tracksTPC[iTrk]; const bool isGoodTrack = checkTrack(trackFull); @@ -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); @@ -1512,6 +1512,7 @@ class TPCTimeSeries : public Task << "vertexTime=" << vertexTime /// time stamp assigned to the vertex << "trackTime0=" << trackTime0 /// time stamp assigned to the track << "TOFmask=" << std::get<6>(idxTPCTrackToTOFCluster[iTrk]) /// delta T- TPC TOF + << "TOFchannel=" << std::get<7>(idxTPCTrackToTOFCluster[iTrk]) /// TOF channel inside a sector // TPC delta param << "deltaTPCParamInOutTgl=" << deltaTPCParamInOutTgl << "deltaTPCParamInOutQPt=" << deltaTPCParamInOutQPt @@ -1824,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/ZSSpec.cxx b/Detectors/TPC/workflow/src/ZSSpec.cxx index ccd59de42f000..c24647f6ae240 100644 --- a/Detectors/TPC/workflow/src/ZSSpec.cxx +++ b/Detectors/TPC/workflow/src/ZSSpec.cxx @@ -22,7 +22,7 @@ #include "DataFormatsTPC/ZeroSuppression.h" #include "DataFormatsTPC/Helpers.h" #include "DataFormatsTPC/Digit.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUHostDataTypes.h" #include "GPUO2InterfaceConfiguration.h" #include "TPCBase/Sector.h" diff --git a/Detectors/TPC/workflow/src/entropy-encoder-workflow.cxx b/Detectors/TPC/workflow/src/entropy-encoder-workflow.cxx index 3f9029cf384a9..3256b875447a8 100644 --- a/Detectors/TPC/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/TPC/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}, ConfigParamSpec{"inputFromFile", VariantType::Bool, false, {"Expect COMPCLUSTERS from file"}}}; @@ -38,6 +39,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::tpc::getEntropyEncoderSpec(cfgc.options().get("inputFromFile"), cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::tpc::getEntropyEncoderSpec(cfgc.options().get("inputFromFile"), cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/TPC/workflow/src/time-series-merge-integrator.cxx b/Detectors/TPC/workflow/src/time-series-merge-integrator.cxx deleted file mode 100644 index c17b68e307328..0000000000000 --- a/Detectors/TPC/workflow/src/time-series-merge-integrator.cxx +++ /dev/null @@ -1,34 +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 "TPCWorkflow/TPCMergeTimeSeriesSpec.h" -#include "CommonUtils/ConfigurableParam.h" -#include "Framework/ConfigParamSpec.h" - -using namespace o2::framework; - -void customize(std::vector& workflowOptions) -{ - std::vector options{ - ConfigParamSpec{"configKeyValues", VariantType::String, "", {"Semicolon separated key=value strings"}}, - }; - std::swap(workflowOptions, options); -} - -#include "Framework/runDataProcessing.h" - -WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) -{ - WorkflowSpec wf; - o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::tpc::getTPCMergeTimeSeriesSpec()); - return wf; -} 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-miptrack-filter.cxx b/Detectors/TPC/workflow/src/tpc-miptrack-filter.cxx index 112e8ff2cd3a4..ae05b0c431626 100644 --- a/Detectors/TPC/workflow/src/tpc-miptrack-filter.cxx +++ b/Detectors/TPC/workflow/src/tpc-miptrack-filter.cxx @@ -13,6 +13,10 @@ #include "TPCWorkflow/MIPTrackFilterSpec.h" #include "Framework/ConfigParamSpec.h" #include "DataFormatsTPC/TrackTPC.h" +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" + +using GID = o2::dataformats::GlobalTrackID; template using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; @@ -21,6 +25,8 @@ void customize(std::vector& workflowOptions) { std::vector options{ {"enable-writer", VariantType::Bool, false, {"selection string input specs"}}, + {"use-global-tracks", VariantType::Bool, false, {"use global matched tracks instead of TPC only"}}, + {"disable-root-input", VariantType::Bool, false, {"disable root-files input reader"}}, }; std::swap(workflowOptions, options); @@ -33,8 +39,17 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) { using namespace o2::tpc; + const auto useGlobal = config.options().get("use-global-tracks"); WorkflowSpec workflow; - workflow.emplace_back(getMIPTrackFilterSpec()); + + const auto useMC = false; + auto srcTracks = GID::getSourcesMask("TPC"); + const auto srcCls = GID::getSourcesMask(""); + if (useGlobal) { + srcTracks = GID::getSourcesMask("ITS,TPC,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + } + + workflow.emplace_back(getMIPTrackFilterSpec(srcTracks)); if (config.options().get("enable-writer")) { const char* processName = "tpc-mips-writer"; diff --git a/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx b/Detectors/TPC/workflow/src/tpc-reco-workflow.cxx index 9d7ab63b0c2a0..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" @@ -57,8 +58,8 @@ void customize(std::vector& workflowOptions) using namespace o2::framework; std::vector options{ - {"input-type", VariantType::String, "digits", {"digitizer, digits, zsraw, clustershw, clusters, compressed-clusters, compressed-clusters-ctf, pass-through"}}, - {"output-type", VariantType::String, "tracks", {"digits, zsraw, clustershw, clusters, tracks, compressed-clusters, encoded-clusters, disable-writer, send-clusters-per-sector, qa, no-shared-cluster-map, tpc-triggers"}}, + {"input-type", VariantType::String, "digits", {"digitizer, digits, zsraw, clustershw, clusters, compressed-clusters-root, compressed-clusters-flat, compressed-clusters-flat-for-encode, pass-through"}}, + {"output-type", VariantType::String, "tracks", {"digits, zsraw, clustershw, clusters, tracks, compressed-clusters-root, compressed-clusters-flat, encoded-clusters, disable-writer, send-clusters-per-sector, qa, no-shared-cluster-map, tpc-triggers"}}, {"disable-root-input", o2::framework::VariantType::Bool, false, {"disable root-files input reader"}}, {"no-ca-clusterer", VariantType::Bool, false, {"Use HardwareClusterer instead of clusterer of GPUCATracking"}}, {"disable-mc", VariantType::Bool, false, {"disable sending of MC information"}}, @@ -71,11 +72,12 @@ void customize(std::vector& workflowOptions) {"configFile", VariantType::String, "", {"configuration file for configurable parameters"}}, {"filtered-input", VariantType::Bool, false, {"Filtered tracks, clusters input, prefix dataDescriptors with F"}}, {"select-ir-frames", VariantType::Bool, false, {"Subscribe and filter according to external IR Frames"}}, + {"ctf-dict", VariantType::String, "none", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, {"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); } @@ -155,8 +157,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) gDispatchTrigger = o2::framework::ConcreteDataTypeMatcher{"TPC", "DIGITS"}; } else if (inputType == "clustershw") { gDispatchTrigger = o2::framework::ConcreteDataTypeMatcher{"TPC", "CLUSTERHW"}; - } else if (inputType == "clustersnative") { - gDispatchTrigger = o2::framework::ConcreteDataTypeMatcher{"TPC", "CLUSTERNATIVE"}; } else if (inputType == "zsraw") { gDispatchTrigger = o2::framework::ConcreteDataTypeMatcher{"TPC", "RAWDATA"}; } @@ -170,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 @@ -184,6 +184,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) !cfgc.options().get("no-ca-clusterer"), // !cfgc.options().get("no-tpc-zs-on-the-fly"), // !cfgc.options().get("ignore-dist-stf"), // + cfgc.options().get("ctf-dict"), cfgc.options().get("select-ir-frames"), cfgc.options().get("filtered-input"), cfgc.options().get("tpc-deadMap-sources"), 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/base/CMakeLists.txt b/Detectors/TRD/base/CMakeLists.txt index 030fb6cea1e50..e0563a85a3f42 100644 --- a/Detectors/TRD/base/CMakeLists.txt +++ b/Detectors/TRD/base/CMakeLists.txt @@ -16,7 +16,6 @@ o2_add_library(TRDBase src/GeometryFlat.cxx src/PadResponse.cxx src/FeeParam.cxx - src/RecoParam.cxx src/ChamberStatus.cxx src/Calibrations.cxx src/CalOnlineGainTables.cxx @@ -38,7 +37,6 @@ o2_target_root_dictionary(TRDBase include/TRDBase/GeometryFlat.h include/TRDBase/PadResponse.h include/TRDBase/FeeParam.h - include/TRDBase/RecoParam.h include/TRDBase/Calibrations.h include/TRDBase/PadParameters.h include/TRDBase/PadCalibrations.h diff --git a/Detectors/TRD/base/include/TRDBase/RecoParam.h b/Detectors/TRD/base/include/TRDBase/RecoParam.h deleted file mode 100644 index 1828a0b1724e9..0000000000000 --- a/Detectors/TRD/base/include/TRDBase/RecoParam.h +++ /dev/null @@ -1,64 +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 RecoParam.h -/// \brief Error parameterizations and helper functions for TRD reconstruction -/// \author Ole Schmidt - -#ifndef O2_TRD_RECOPARAM_H -#define O2_TRD_RECOPARAM_H - -#include -#include "Rtypes.h" - -namespace o2 -{ -namespace trd -{ - -class RecoParam -{ - public: - RecoParam() = default; - RecoParam(const RecoParam&) = default; - ~RecoParam() = default; - - /// Load parameterization for given magnetic field - void setBfield(float bz); - - /// Recalculate tracklet covariance based on phi angle of related track - void recalcTrkltCov(const float tilt, const float snp, const float rowSize, std::array& cov) const; - - /// Get tracklet r-phi resolution for given phi angle - /// Resolution depends on the track angle sin(phi) = snp and is approximated by the formula - /// sigma_y(snp) = sqrt(a^2 + c^2 * (snp - b^2)^2) - /// more details are given in http://cds.cern.ch/record/2724259 in section 5.3.3 - /// \param phi angle of related track - /// \return sigma_y^2 of tracklet - float getRPhiRes(float snp) const { return (mA2 + mC2 * (snp - mB) * (snp - mB)); } - - /// Get tracklet z correction coefficient for track-eta based corraction - float getZCorrCoeffNRC() const { return mZCorrCoefNRC; } - - private: - // tracklet error parameterization depends on the magnetic field - float mA2{1.f}; ///< parameterization for tracklet position resolution - float mB{0.f}; ///< parameterization for tracklet position resolution - float mC2{0.f}; ///< parameterization for tracklet position resolution - float mZCorrCoefNRC{1.4f}; ///< tracklet z-position depends linearly on track dip angle - - ClassDefNV(RecoParam, 1); -}; - -} // namespace trd -} // namespace o2 - -#endif // O2_TRD_RECOPARAM_H diff --git a/Detectors/TRD/base/src/CalSingleChamberStatus.cxx b/Detectors/TRD/base/src/CalSingleChamberStatus.cxx deleted file mode 100644 index f054d49766461..0000000000000 --- a/Detectors/TRD/base/src/CalSingleChamberStatus.cxx +++ /dev/null @@ -1,154 +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. - -/////////////////////////////////////////////////////////////////////////////// -// // -// Calibration base class for a single ROC // -// Contains one char value per pad // -// // -/////////////////////////////////////////////////////////////////////////////// - -#include "TRDBase/CalSingleChamberStatus.h" - -using namespace o2::trd; - -//_____________________________________________________________________________ -CalSingleChamberStatus::CalSingleChamberStatus() = default; - -//_____________________________________________________________________________ -CalSingleChamberStatus::CalSingleChamberStatus(Int_t p, Int_t c, Int_t cols) - : mPla(p), mCha(c), mNcols(cols) -{ - // - // Constructor that initializes a given pad plane type - // - - // - // The pad plane parameter - // - switch (p) { - case 0: - if (c == 2) { - // L0C0 type - mNrows = 12; - } else { - // L0C1 type - mNrows = 16; - } - break; - case 1: - if (c == 2) { - // L1C0 type - mNrows = 12; - } else { - // L1C1 type - mNrows = 16; - } - break; - case 2: - if (c == 2) { - // L2C0 type - mNrows = 12; - } else { - // L2C1 type - mNrows = 16; - } - break; - case 3: - if (c == 2) { - // L3C0 type - mNrows = 12; - } else { - // L3C1 type - mNrows = 16; - } - break; - case 4: - if (c == 2) { - // L4C0 type - mNrows = 12; - } else { - // L4C1 type - mNrows = 16; - } - break; - case 5: - if (c == 2) { - // L5C0 type - mNrows = 12; - } else { - // L5C1 type - mNrows = 16; - } - break; - }; - - mNchannels = mNrows * mNcols; - if (mNchannels != 0) { - mData.resize(mNchannels); - } - memset(&mData[0], 0, sizeof(mData[0]) * mData.size()); -} - -//_____________________________________________________________________________ -CalSingleChamberStatus::CalSingleChamberStatus(const CalSingleChamberStatus& c) - : mPla(c.mPla), mCha(c.mCha), mNrows(c.mNrows), mNcols(c.mNcols), mNchannels(c.mNchannels) -{ - // - // CalSingleChamberStatus copy constructor - // - - mData = c.mData; -} - -//_____________________________________________________________________________ -CalSingleChamberStatus::~CalSingleChamberStatus() = default; - -//_____________________________________________________________________________ -CalSingleChamberStatus& CalSingleChamberStatus::operator=(const CalSingleChamberStatus& c) -{ - // - // Assignment operator - // - - if (this == &c) { - return *this; - } - - mPla = c.mPla; - mCha = c.mCha; - mNrows = c.mNrows; - mNcols = c.mNcols; - mNchannels = c.mNchannels; - mData = c.mData; - - return *this; -} - -//_____________________________________________________________________________ -void CalSingleChamberStatus::Copy(CalSingleChamberStatus& c) const -{ - // - // Copy function - // - - Int_t iBin = 0; - - c.mPla = mPla; - c.mCha = mCha; - - c.mNrows = mNrows; - c.mNcols = mNcols; - - c.mNchannels = mNchannels; - - c.mData = mData; -} diff --git a/Detectors/TRD/base/src/RecoParam.cxx b/Detectors/TRD/base/src/RecoParam.cxx deleted file mode 100644 index 34921777bdb72..0000000000000 --- a/Detectors/TRD/base/src/RecoParam.cxx +++ /dev/null @@ -1,64 +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 RecoParam.cxx -/// \brief Error parameterizations and helper functions for TRD reconstruction -/// \author Ole Schmidt - -#include "TRDBase/RecoParam.h" -#include -#include - -using namespace o2::trd; - -// error parameterizations taken from http://cds.cern.ch/record/2724259 Appendix A -void RecoParam::setBfield(float bz) -{ - if (std::fabs(std::fabs(bz) - 2) < 0.1) { - if (bz > 0) { - // magnetic field +0.2 T - mA2 = 1.6e-3f; - mB = -1.43e-2f; - mC2 = 4.55e-2f; - } else { - // magnetic field -0.2 T - mA2 = 1.6e-3f; - mB = 1.43e-2f; - mC2 = 4.55e-2f; - } - } else if (std::fabs(std::fabs(bz) - 5) < 0.1) { - if (bz > 0) { - // magnetic field +0.5 T - mA2 = 1.6e-3f; - mB = 0.125f; - mC2 = 0.0961f; - } else { - // magnetic field -0.5 T - mA2 = 1.6e-3f; - mB = -0.14f; - mC2 = 0.1156f; - } - } else { - LOG(warning) << "No error parameterization available for Bz= " << bz << ". Keeping default value (sigma_y = const. = 1cm)"; - } - LOG(info) << "Loaded error parameterization for Bz = " << bz; -} - -void RecoParam::recalcTrkltCov(const float tilt, const float snp, const float rowSize, std::array& cov) const -{ - float t2 = tilt * tilt; // tan^2 (tilt) - float c2 = 1.f / (1.f + t2); // cos^2 (tilt) - float sy2 = getRPhiRes(snp); - float sz2 = rowSize * rowSize / 12.f; - cov[0] = c2 * (sy2 + t2 * sz2); - cov[1] = c2 * tilt * (sz2 - sy2); - cov[2] = c2 * (t2 * sy2 + sz2); -} diff --git a/Detectors/TRD/base/src/TRDBaseLinkDef.h b/Detectors/TRD/base/src/TRDBaseLinkDef.h index 2d3de311a1dc0..a835def5628b2 100644 --- a/Detectors/TRD/base/src/TRDBaseLinkDef.h +++ b/Detectors/TRD/base/src/TRDBaseLinkDef.h @@ -19,7 +19,6 @@ #pragma link C++ class o2::trd::Geometry + ; #pragma link C++ class o2::trd::GeometryBase + ; #pragma link C++ class o2::trd::FeeParam + ; -#pragma link C++ class o2::trd::RecoParam + ; #pragma link C++ class o2::trd::PadResponse + ; #pragma link C++ class o2::trd::PadParameters < float > +; #pragma link C++ class o2::trd::PadParameters < char> + ; diff --git a/Detectors/TRD/base/src/TrackletTransformer.cxx b/Detectors/TRD/base/src/TrackletTransformer.cxx index 58938cfd99161..7f9976b8ce714 100644 --- a/Detectors/TRD/base/src/TrackletTransformer.cxx +++ b/Detectors/TRD/base/src/TrackletTransformer.cxx @@ -50,7 +50,8 @@ float TrackletTransformer::calculateDy(int detector, int slope, const PadPlane* // NOTE: check what drift height is used in calibration code to ensure consistency // NOTE: check sign convention of Lorentz angle // NOTE: confirm the direction in which vDrift is measured/determined. Is it in x or in direction of drift? - double lorentzCorrection = TMath::Tan(exb) * mXAnode; + // The Lorentz correction have to be applied both at the point of entrance and at the end of the drift region + double lorentzCorrection = TMath::Tan(exb) * mGeo->cdrHght(); // assuming angle in Bailhache, fig. 4.17 would be positive in our calibration code double calibratedDy = rawDy - lorentzCorrection; diff --git a/Detectors/TRD/calibration/CMakeLists.txt b/Detectors/TRD/calibration/CMakeLists.txt index 36d00e92bbc16..52444d2855b1f 100644 --- a/Detectors/TRD/calibration/CMakeLists.txt +++ b/Detectors/TRD/calibration/CMakeLists.txt @@ -28,6 +28,7 @@ o2_add_library(TRDCalibration O2::DetectorsBase O2::DetectorsCalibration O2::MathUtils + O2::GPUTracking O2::DetectorsDCS) o2_target_root_dictionary(TRDCalibration diff --git a/Detectors/TRD/calibration/README.md b/Detectors/TRD/calibration/README.md index bdbfc9e709800..f769fa99b5778 100644 --- a/Detectors/TRD/calibration/README.md +++ b/Detectors/TRD/calibration/README.md @@ -27,15 +27,15 @@ For 'o2-calibration-trd-workflow --vDriftAndExB' there are also the following ke *Hint: You can get information on the meaning of the parameters by running `o2-calibration-trd-workflow --vDriftAndExB -b --help full`* -If you want to run the calibration from a local file with residuals, trdangreshistos.root, you can run: +If you want to run the calibration from a local file with residuals, trdcaliboutput.root, you can run: - o2-calibration-trd-workflow --vDriftAndExB -b --enable-root-input --calib-vdexb-calibration '--tf-per-slot 1' --configKeyValues "TRDCalibParams.minEntriesChamber=100;TRDCalibParams.minEntriesTotal=50000" + o2-calibration-trd-workflow --vDriftAndExB -b --enable-root-input --calib-vdexb-calibration '--tf-per-slot 1' --configKeyValues "TRDCalibParams.minEntriesChamber=100;TRDCalibParams.minEntriesTotal=50000" --trd-calib-infile trdcaliboutput.root Additionally it is possible to perform the calibrations fit manually per chamber if you have TPC-TRD or ITS-TPC-TRD tracks, you can run: o2-trd-global-tracking -b --enable-trackbased-calib -This produces `trdangreshistos.root` which holds the residuals of the angles and differences. +This produces `trdcaliboutput.root` which holds the residuals of the angles and differences. Then run the macro `Detectors/TRD/calibration/macros/manualCalibFit.C`. This produces a file of similar name with the fitted data and prints out the fit results. This is equivalent to running: diff --git a/Detectors/TRD/calibration/include/TRDCalibration/CalibrationParams.h b/Detectors/TRD/calibration/include/TRDCalibration/CalibrationParams.h index 677673a7f85f3..cadd21af2a55d 100644 --- a/Detectors/TRD/calibration/include/TRDCalibration/CalibrationParams.h +++ b/Detectors/TRD/calibration/include/TRDCalibration/CalibrationParams.h @@ -25,12 +25,13 @@ namespace trd /// VDrift and ExB calibration parameters. struct TRDCalibParams : public o2::conf::ConfigurableParamHelper { unsigned int nTrackletsMin = 5; ///< minimum amount of tracklets + unsigned int nTrackletsMinLoose = 4; ///< minimum amount of tracklets if two layers with a large lever arm both have a hit unsigned int chi2RedMax = 6; ///< maximum reduced chi2 acceptable for track quality - size_t minEntriesChamber = 75; ///< minimum number of entries per chamber to fit single time slot - size_t minEntriesTotal = 40'500; ///< minimum total required for meaningful fits + size_t minEntriesChamber = 200; ///< minimum number of entries per chamber to fit single time slot + size_t minEntriesTotal = 400'000; ///< minimum total required for meaningful fits // For gain calibration - unsigned int nTrackletsMinGainCalib = 5; + unsigned int nTrackletsMinGainCalib = 3; size_t minEntriesChamberGainCalib = 500; ///< minimum number of entries per chamber to fit single time slot size_t minEntriesTotalGainCalib = 1'000'000; ///< minimum total required for meaningful fits // Cuts for selecting clean pion candidates for gain calibration diff --git a/Detectors/TRD/calibration/include/TRDCalibration/CalibratorVdExB.h b/Detectors/TRD/calibration/include/TRDCalibration/CalibratorVdExB.h index 7d55850af9fd4..16223f3e78112 100644 --- a/Detectors/TRD/calibration/include/TRDCalibration/CalibratorVdExB.h +++ b/Detectors/TRD/calibration/include/TRDCalibration/CalibratorVdExB.h @@ -88,8 +88,8 @@ class CalibratorVdExB final : public o2::calibration::TimeSlotCalibration mOutFile{nullptr}; ///< output file std::unique_ptr mOutTree{nullptr}; ///< output tree diff --git a/Detectors/TRD/calibration/include/TRDCalibration/PulseHeight.h b/Detectors/TRD/calibration/include/TRDCalibration/PulseHeight.h index 3fc70603da7d5..52305cc585b34 100644 --- a/Detectors/TRD/calibration/include/TRDCalibration/PulseHeight.h +++ b/Detectors/TRD/calibration/include/TRDCalibration/PulseHeight.h @@ -61,6 +61,7 @@ class PulseHeight /// Access to output const std::vector& getPHData() { return mPHValues; } + const std::vector& getPHDataHD() { return mPHValuesHD; } void createOutputFile(); void closeOutputFile(); @@ -77,6 +78,7 @@ class PulseHeight // output std::vector mPHValues, *mPHValuesPtr{&mPHValues}; ///< vector of values used to fill the PH spectra per detector + std::vector mPHValuesHD, *mPHValuesHDPtr{&mPHValuesHD}; ///< vector of values used to fill the High definition PH spectra per detector with pretrigger phase std::vector mDistances, *mDistancesPtr{&mDistances}; ///< pad distance between tracklet column and digit ADC maximum std::unique_ptr mOutFile{nullptr}; ///< output file std::unique_ptr mOutTree{nullptr}; ///< output tree diff --git a/Detectors/TRD/calibration/include/TRDCalibration/TrackBasedCalib.h b/Detectors/TRD/calibration/include/TRDCalibration/TrackBasedCalib.h index 49ba9fdf3d161..7249016d9675e 100644 --- a/Detectors/TRD/calibration/include/TRDCalibration/TrackBasedCalib.h +++ b/Detectors/TRD/calibration/include/TRDCalibration/TrackBasedCalib.h @@ -24,7 +24,7 @@ #include "DataFormatsTRD/NoiseCalibration.h" #include "TRDBase/PadCalibrationsAliases.h" #include "DetectorsBase/Propagator.h" -#include "TRDBase/RecoParam.h" +#include "GPUTRDRecoParam.h" #include "Rtypes.h" @@ -90,7 +90,7 @@ class TrackBasedCalib float mMaxSnp{o2::base::Propagator::MAX_SIN_PHI}; ///< max snp when propagating tracks float mMaxStep{o2::base::Propagator::MAX_STEP}; ///< maximum step for propagation MatCorrType mMatCorr{MatCorrType::USEMatCorrNONE}; ///< if material correction should be done - RecoParam mRecoParam; ///< parameters required for TRD reconstruction + o2::gpu::GPUTRDRecoParam mRecoParam; ///< parameters required for TRD reconstruction AngularResidHistos mAngResHistos; ///< aggregated data for the track based calibration std::vector mGainCalibHistos; ///< aggregated input data for gain calibration float bz; ///< magnetic field diff --git a/Detectors/TRD/calibration/macros/manualCalibFit.C b/Detectors/TRD/calibration/macros/manualCalibFit.C index d31744a2e727c..877202d4e211e 100644 --- a/Detectors/TRD/calibration/macros/manualCalibFit.C +++ b/Detectors/TRD/calibration/macros/manualCalibFit.C @@ -30,18 +30,19 @@ // O2 header #include +#include "DetectorsBase/Propagator.h" #endif // This root macro reads in 'trdangreshistos.root' and // performs the calibration fits manually as in CalibratorVdExB.cxx // This can be used for checking if the calibration fits make sense. -void manualCalibFit() +void manualCalibFit(int runNumber = 563335, bool usePreCorrFromCCDB = false) { //---------------------------------------------------- // TTree and File //---------------------------------------------------- - std::unique_ptr inFilePtr(TFile::Open("trdangreshistos.root")); + std::unique_ptr inFilePtr(TFile::Open("trdcaliboutput.root")); if (inFilePtr == nullptr) { printf("Input File could not be read!\n'"); return; @@ -60,18 +61,46 @@ void manualCalibFit() tree->SetBranchAddress("mHistogramEntries[13500]", &mHistogramEntries); tree->SetBranchAddress("mNEntriesPerBin[13500]", &mNEntriesPerBin); + // use precorr values from ccdb + // necessary when the angular residuals were calculated already using ccdb calibration (e.g. in a local run) + + o2::trd::CalVdriftExB* calObject; + if (usePreCorrFromCCDB) { + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + + o2::ccdb::CcdbApi ccdb; + ccdb.init("http://alice-ccdb.cern.ch"); + auto runDuration = ccdbmgr.getRunDuration(runNumber); + + std::map metadata; + std::map headers; + + calObject = ccdb.retrieveFromTFileAny("TRD/Calib/CalVdriftExB", metadata, runDuration.first + 60000, &headers, "", "", "1689478811721"); + } + //---------------------------------------------------- // Configure Fitter //---------------------------------------------------- o2::trd::FitFunctor mFitFunctor; std::array, 540> profiles; ///< profile histograms for each TRD chamber + int counter = 0; for (int iDet = 0; iDet < 540; ++iDet) { mFitFunctor.profiles[iDet] = std::make_unique(Form("profAngleDiff_%i", iDet), Form("profAngleDiff_%i", iDet), 25, -25.f, 25.f); + if (usePreCorrFromCCDB) { + if (calObject->isGoodExB(iDet)) + counter++; + mFitFunctor.vdPreCorr[iDet] = calObject->getVdrift(iDet, true); + mFitFunctor.laPreCorr[iDet] = calObject->getExB(iDet, true); + } } + std::cout << counter << " good entries in the CCDB " << std::endl; + mFitFunctor.mAnodePlane = 3.35; // don't really care as long as it's not zero, this parameter could be removed mFitFunctor.lowerBoundAngleFit = 80 * TMath::DegToRad(); mFitFunctor.upperBoundAngleFit = 100 * TMath::DegToRad(); - mFitFunctor.vdPreCorr.fill(1.546); - mFitFunctor.laPreCorr.fill(0.0); + if (!usePreCorrFromCCDB) { + mFitFunctor.vdPreCorr.fill(1.546); + mFitFunctor.laPreCorr.fill(0.0); + } //---------------------------------------------------- // Loop @@ -88,15 +117,18 @@ void manualCalibFit() //---------------------------------------------------- // Fill profiles //---------------------------------------------------- + int nEntriesDetTotal[540] = {}; for (int iDet = 0; iDet < 540; ++iDet) { for (int iBin = 0; iBin < 25; ++iBin) { auto angleDiffSum = mHistogramEntriesSum[iDet * 25 + iBin]; auto nEntries = mNEntriesPerBinSum[iDet * 25 + iBin]; + nEntriesDetTotal[iDet] += nEntries; if (nEntries > 0) { // skip entries which have no entries; ? // add to the respective profile for fitting later on mFitFunctor.profiles[iDet]->Fill(2 * iBin - 25.f, angleDiffSum / nEntries, nEntries); } } + printf("Det %d: nEntries=%d \n", iDet, nEntriesDetTotal[iDet]); } //---------------------------------------------------- @@ -105,16 +137,23 @@ void manualCalibFit() printf("-------- Started fits\n"); std::array laFitResults{}; std::array vdFitResults{}; + + TH1F* hVd = new TH1F("hVd", "v drift", 150, 0.5, 2.); + TH1F* hLa = new TH1F("hLa", "lorentz angle", 200, -25., 25.); + o2::trd::CalVdriftExB* calObjectOut = new o2::trd::CalVdriftExB(); + for (int iDet = 0; iDet < 540; ++iDet) { + if (nEntriesDetTotal[iDet] < 75) + continue; mFitFunctor.currDet = iDet; ROOT::Fit::Fitter fitter; double paramsStart[2]; - paramsStart[0] = 0. * TMath::DegToRad(); + paramsStart[0] = 0.; paramsStart[1] = 1.; fitter.SetFCN(2, mFitFunctor, paramsStart); fitter.Config().ParSettings(0).SetLimits(-0.7, 0.7); fitter.Config().ParSettings(0).SetStepSize(.01); - fitter.Config().ParSettings(1).SetLimits(0., 3.); + fitter.Config().ParSettings(1).SetLimits(0.01, 3.); fitter.Config().ParSettings(1).SetStepSize(.01); ROOT::Math::MinimizerOptions opt; opt.SetMinimizerType("Minuit2"); @@ -127,14 +166,28 @@ void manualCalibFit() auto fitResult = fitter.Result(); laFitResults[iDet] = fitResult.Parameter(0); vdFitResults[iDet] = fitResult.Parameter(1); - printf("Det %d: la=%f\tvd=%f\n", iDet, laFitResults[iDet] * TMath::RadToDeg(), vdFitResults[iDet]); + if (fitResult.MinFcnValue() > 0.03) + continue; + printf("Det %d: la=%.3f \tvd=%.3f \t100*minValue=%f \tentries=%d\n", iDet, laFitResults[iDet] * TMath::RadToDeg(), vdFitResults[iDet], 100 * fitResult.MinFcnValue(), nEntriesDetTotal[iDet]); + hVd->Fill(vdFitResults[iDet]); + hLa->Fill(laFitResults[iDet] * TMath::RadToDeg()); + calObjectOut->setVdrift(iDet, vdFitResults[iDet]); + calObjectOut->setExB(iDet, laFitResults[iDet]); } printf("-------- Finished fits\n"); + std::cout << "number of chambers with enough entries: " << hVd->GetEntries() << std::endl; + ; + std::cout << "vdrift mean: " << hVd->GetMean() << " sigma: " << hVd->GetStdDev() << std::endl; + std::cout << "lorentz angle mean: " << hLa->GetMean() << " sigma: " << hLa->GetStdDev() << std::endl; + //---------------------------------------------------- // Write //---------------------------------------------------- std::unique_ptr outFilePtr(TFile::Open("manualCalibFit.root", "RECREATE")); + hVd->Write(); + hLa->Write(); + outFilePtr->WriteObjectAny(calObjectOut, "o2::trd::CalVdriftExB", "calObject"); for (auto& p : mFitFunctor.profiles) p->Write(); } diff --git a/Detectors/TRD/calibration/src/CalibratorGain.cxx b/Detectors/TRD/calibration/src/CalibratorGain.cxx index 77efeaeb36f1e..c276563cac5fb 100644 --- a/Detectors/TRD/calibration/src/CalibratorGain.cxx +++ b/Detectors/TRD/calibration/src/CalibratorGain.cxx @@ -89,7 +89,7 @@ void CalibratorGain::retrievePrev(o2::framework::ProcessingContext& pc) std::string msg = "Default Object"; // We check if the object we got is the default one by comparing it to the defaults. for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) { - if (dataCalGain->getMPVdEdx(iDet) != constants::MPVDEDXDEFAULT) { + if (dataCalGain->getMPVdEdx(iDet, false) != constants::MPVDEDXDEFAULT) { msg = "Previous Object"; break; } @@ -98,7 +98,7 @@ void CalibratorGain::retrievePrev(o2::framework::ProcessingContext& pc) // Here we set each entry regardless if it is the default or not. for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) { - mFitResults[iDet] = dataCalGain->getMPVdEdx(iDet); + mFitResults[iDet] = dataCalGain->getMPVdEdx(iDet, false); } } diff --git a/Detectors/TRD/calibration/src/CalibratorVdExB.cxx b/Detectors/TRD/calibration/src/CalibratorVdExB.cxx index 64a8664640e41..961a74eefbe0f 100644 --- a/Detectors/TRD/calibration/src/CalibratorVdExB.cxx +++ b/Detectors/TRD/calibration/src/CalibratorVdExB.cxx @@ -24,6 +24,7 @@ #include "CCDB/BasicCCDBManager.h" #include "CommonUtils/NameConf.h" #include "CommonUtils/MemFileHelper.h" +#include "DetectorsBase/Propagator.h" #include #include @@ -105,17 +106,37 @@ void CalibratorVdExB::initProcessing() return; } - mFitFunctor.lowerBoundAngleFit = 80 * TMath::DegToRad(); - mFitFunctor.upperBoundAngleFit = 100 * TMath::DegToRad(); + // fit is done in region where ion tails are small, close to lorentz angle + // we want an approximate value of the lorentz angle in order to define better fit boundaries, coinciding with profile bin edges + float bz = o2::base::Propagator::Instance()->getNominalBz(); + // default angle with zero field is slightly shifted + float lorentzAngleAvg = -1.f; + if (TMath::Abs(bz - 2) < 0.1f) { + lorentzAngleAvg = 3.f; + } + if (TMath::Abs(bz + 2) < 0.1f) { + lorentzAngleAvg = -5.f; + } + if (TMath::Abs(bz - 5) < 0.1f) { + lorentzAngleAvg = 7.f; + } + if (TMath::Abs(bz + 5) < 0.1f) { + lorentzAngleAvg = -9.f; + } + + LOGP(info, "b field: {} lorentz angle start: {}", bz, lorentzAngleAvg); + + mFitFunctor.lowerBoundAngleFit = (80 + lorentzAngleAvg) * TMath::DegToRad(); + mFitFunctor.upperBoundAngleFit = (100 + lorentzAngleAvg) * TMath::DegToRad(); mFitFunctor.mAnodePlane = GeometryBase::camHght() / (2.f * 100.f); for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) { mFitFunctor.profiles[iDet] = std::make_unique(Form("profAngleDiff_%i", iDet), Form("profAngleDiff_%i", iDet), NBINSANGLEDIFF, -MAXIMPACTANGLE, MAXIMPACTANGLE); } mFitter.SetFCN(2, mFitFunctor, mParamsStart); - mFitter.Config().ParSettings(ParamIndex::LA).SetLimits(-0.7, 0.7); + mFitter.Config().ParSettings(ParamIndex::LA).SetLimits(constants::EXBMIN, constants::EXBMAX); mFitter.Config().ParSettings(ParamIndex::LA).SetStepSize(.01); - mFitter.Config().ParSettings(ParamIndex::VD).SetLimits(0.01, 3.); + mFitter.Config().ParSettings(ParamIndex::VD).SetLimits(constants::VDRIFTMIN, constants::VDRIFTMAX); mFitter.Config().ParSettings(ParamIndex::VD).SetStepSize(.01); ROOT::Math::MinimizerOptions opt; opt.SetMinimizerType("Minuit2"); @@ -145,8 +166,8 @@ void CalibratorVdExB::retrievePrev(o2::framework::ProcessingContext& pc) std::string msg = "Default Object"; // We check if the object we got is the default one by comparing it to the defaults. for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) { - if (dataCalVdriftExB->getVdrift(iDet) != VDRIFTDEFAULT || - dataCalVdriftExB->getExB(iDet) != EXBDEFAULT) { + if (dataCalVdriftExB->getVdrift(iDet, false) != VDRIFTDEFAULT || + dataCalVdriftExB->getExB(iDet, false) != EXBDEFAULT) { msg = "Previous Object"; break; } @@ -155,8 +176,8 @@ void CalibratorVdExB::retrievePrev(o2::framework::ProcessingContext& pc) // Here we set each entry regardless if it is the default or not. for (int iDet = 0; iDet < MAXCHAMBER; ++iDet) { - mFitFunctor.laPreCorr[iDet] = dataCalVdriftExB->getExB(iDet); - mFitFunctor.vdPreCorr[iDet] = dataCalVdriftExB->getVdrift(iDet); + mFitFunctor.laPreCorr[iDet] = dataCalVdriftExB->getExB(iDet, false); + mFitFunctor.vdPreCorr[iDet] = dataCalVdriftExB->getVdrift(iDet, false); } } @@ -184,17 +205,30 @@ void CalibratorVdExB::finalizeSlot(Slot& slot) } // Check if we have the minimum amount of entries if (sumEntries < mMinEntriesChamber) { - LOGF(debug, "Chamber %d did not reach minimum amount of entries for refit", iDet); + LOGF(debug, "Chamber %d did not reach minimum amount of entries for refit: %d", iDet, sumEntries); continue; } + float laPreCorrTemp = mFitFunctor.laPreCorr[iDet]; + float vdPreCorrTemp = mFitFunctor.vdPreCorr[iDet]; + // Here we start from uncalibrated values, otherwise online calibration does not work properly + mFitFunctor.laPreCorr[iDet] = EXBDEFAULT; + mFitFunctor.vdPreCorr[iDet] = VDRIFTDEFAULT; + // Reset Start Parameter mParamsStart[ParamIndex::LA] = 0.0; mParamsStart[ParamIndex::VD] = 1.0; mFitter.FitFCN(); auto fitResult = mFitter.Result(); + if (fitResult.MinFcnValue() > 0.03) { + LOGF(debug, "Chamber %d fit did not converge properly, minimization value too high: %f", iDet, fitResult.MinFcnValue()); + // The fit did not work properly, so we keep previous values + mFitFunctor.laPreCorr[iDet] = laPreCorrTemp; + mFitFunctor.vdPreCorr[iDet] = vdPreCorrTemp; + continue; + } laFitResults[iDet] = fitResult.Parameter(ParamIndex::LA); vdFitResults[iDet] = fitResult.Parameter(ParamIndex::VD); - LOGF(debug, "Fit result for chamber %i: vd=%f, la=%f", iDet, vdFitResults[iDet], laFitResults[iDet] * TMath::RadToDeg()); + LOGF(debug, "Fit result for chamber %i: vd=%f, la=%f, minimizer value=%f", iDet, vdFitResults[iDet], laFitResults[iDet] * TMath::RadToDeg(), fitResult.MinFcnValue()); // Update fit values for next fit mFitFunctor.laPreCorr[iDet] = laFitResults[iDet]; mFitFunctor.vdPreCorr[iDet] = vdFitResults[iDet]; @@ -222,7 +256,7 @@ void CalibratorVdExB::finalizeSlot(Slot& slot) auto flName = o2::ccdb::CcdbApi::generateFileName(clName); std::map metadata; // TODO: do we want to store any meta data? long startValidity = slot.getStartTimeMS() - 10 * o2::ccdb::CcdbObjectInfo::SECOND; - mInfoVector.emplace_back("TRD/Calib/CalVdriftExB", clName, flName, metadata, startValidity, startValidity + o2::ccdb::CcdbObjectInfo::HOUR); + mInfoVector.emplace_back("TRD/Calib/CalVdriftExB", clName, flName, metadata, startValidity, startValidity + 1 * o2::ccdb::CcdbObjectInfo::HOUR); mObjectVector.push_back(calObject); } 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/PulseHeight.cxx b/Detectors/TRD/calibration/src/PulseHeight.cxx index 1981fe528f0f5..44446d40df438 100644 --- a/Detectors/TRD/calibration/src/PulseHeight.cxx +++ b/Detectors/TRD/calibration/src/PulseHeight.cxx @@ -23,6 +23,7 @@ using namespace o2::trd::constants; void PulseHeight::reset() { mPHValues.clear(); + mPHValuesHD.clear(); mDistances.clear(); } @@ -39,6 +40,7 @@ void PulseHeight::createOutputFile() } mOutTree = std::make_unique("ph", "Data points for PH histograms"); mOutTree->Branch("values", &mPHValuesPtr); + mOutTree->Branch("valuesHD", &mPHValuesHDPtr); mOutTree->Branch("dist", &mDistancesPtr); mWriteOutput = true; LOG(info) << "Writing PH data points to local file trd_PH.root"; @@ -178,13 +180,17 @@ void PulseHeight::findDigitsForTracklet(const Tracklet64& trklt, const TriggerRe mDistances.push_back(digitTrackletDistance); for (int iTb = 0; iTb < TIMEBINS; ++iTb) { uint16_t phVal = digit.getADC()[iTb]; + uint16_t phValHD = (digit.getADC()[iTb] << 2) + digit.getPreTrigPhase(); if (left) { phVal += digitLeft->getADC()[iTb]; + phValHD += (digitLeft->getADC()[iTb] << 2) + digit.getPreTrigPhase(); } if (right) { phVal += digitRight->getADC()[iTb]; + phValHD += (digitRight->getADC()[iTb] << 2) + digit.getPreTrigPhase(); } mPHValues.emplace_back(phVal, trkltDet, iTb, nNeighbours, type); + mPHValuesHD.emplace_back(phValHD, trkltDet, iTb, nNeighbours, type); } } } diff --git a/Detectors/TRD/calibration/src/TrackBasedCalib.cxx b/Detectors/TRD/calibration/src/TrackBasedCalib.cxx index 011a888a47618..a0cbab6ac8bfa 100644 --- a/Detectors/TRD/calibration/src/TrackBasedCalib.cxx +++ b/Detectors/TRD/calibration/src/TrackBasedCalib.cxx @@ -13,6 +13,7 @@ /// \brief Provides information required for TRD calibration which is based on the global tracking /// \author Ole Schmidt +#include "GPUO2InterfaceConfiguration.h" #include "TRDCalibration/TrackBasedCalib.h" #include "TRDCalibration/CalibrationParams.h" #include "DataFormatsTRD/Constants.h" @@ -21,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; @@ -35,7 +38,7 @@ void TrackBasedCalib::reset() void TrackBasedCalib::init() { bz = o2::base::Propagator::Instance()->getNominalBz(); - mRecoParam.setBfield(bz); + mRecoParam.init(bz); } void TrackBasedCalib::setInput(const o2::globaltracking::RecoContainer& input) @@ -187,7 +190,10 @@ int TrackBasedCalib::doTrdOnlyTrackFits(gsl::span& tracks) for (const auto& trkIn : tracks) { if (trkIn.getNtracklets() < params.nTrackletsMin) { // with less than 3 tracklets the TRD-only refit not meaningful - continue; + if (trkIn.getNtracklets() < params.nTrackletsMinLoose || !((trkIn.getTrackletIndex(0) >= 0 && (trkIn.getTrackletIndex(NLAYER - 1) >= 0 || trkIn.getTrackletIndex(NLAYER - 2) >= 0))) || (trkIn.getTrackletIndex(1) >= 0 && trkIn.getTrackletIndex(NLAYER - 1) >= 0)) { + // we check if we have enough lever arm, i.e. (first and last) or (second and last) or (first and before last) are present + continue; + } } auto trkWork = trkIn; // input is const, so we need to create a copy bool trackFailed = false; @@ -259,9 +265,20 @@ int TrackBasedCalib::doTrdOnlyTrackFits(gsl::span& tracks) } float trkAngle = o2::math_utils::asin(trkWork.getSnp()) * TMath::RadToDeg(); - float trkltAngle = o2::math_utils::atan(mTrackletsCalib[trkWork.getTrackletIndex(iLayer)].getDy() / Geometry::cdrHght()) * TMath::RadToDeg(); + int trkltId = trkWork.getTrackletIndex(iLayer); + // tracklet angle, corrected for pad tilt + const PadPlane* pad = Geometry::instance()->getPadPlane(mTrackletsRaw[trkltId].getDetector()); + float tilt = tan(TMath::DegToRad() * pad->getTiltingAngle()); // tilt is signed! and returned in degrees + float tiltCorrUp = tilt * trkWork.getTgl() * Geometry::cdrHght(); + float padLength = pad->getRowSize(mTrackletsRaw[trkltId].getPadRow()); + if (!((trkWork.getSigmaZ2() < (padLength * padLength / 12.f)) && (std::fabs(mTrackletsCalib[trkltId].getZ() - trkWork.getZ()) < padLength))) { + tiltCorrUp = 0.f; + } + // use uncalibrated dy because online calibration does not work otherwise + float trkltDy = mTrackletsRaw[trkltId].getUncalibratedDy(30.f / o2::trd::constants::VDRIFTDEFAULT) + tiltCorrUp; + float trkltAngle = o2::math_utils::atan(trkltDy / Geometry::cdrHght()) * TMath::RadToDeg(); float angleDeviation = trkltAngle - trkAngle; - if (mAngResHistos.addEntry(angleDeviation, trkAngle, mTrackletsRaw[trkWork.getTrackletIndex(iLayer)].getDetector())) { + if (mAngResHistos.addEntry(angleDeviation, trkAngle, mTrackletsRaw[trkltId].getDetector())) { // track impact angle out of histogram range continue; } 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/qc/CMakeLists.txt b/Detectors/TRD/qc/CMakeLists.txt index d631de1f54246..daba4928957f9 100644 --- a/Detectors/TRD/qc/CMakeLists.txt +++ b/Detectors/TRD/qc/CMakeLists.txt @@ -21,6 +21,7 @@ o2_add_library(TRDQC O2::DataFormatsTRD O2::DataFormatsGlobalTracking O2::DetectorsBase + O2::GPUTracking O2::MathUtils) o2_target_root_dictionary(TRDQC diff --git a/Detectors/TRD/qc/include/TRDQC/Tracking.h b/Detectors/TRD/qc/include/TRDQC/Tracking.h index 880b1727ab367..f39c64286d0cc 100644 --- a/Detectors/TRD/qc/include/TRDQC/Tracking.h +++ b/Detectors/TRD/qc/include/TRDQC/Tracking.h @@ -25,7 +25,7 @@ #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DataFormatsTPC/TrackTPC.h" #include "DetectorsBase/Propagator.h" -#include "TRDBase/RecoParam.h" +#include "GPUTRDRecoParam.h" #include "Rtypes.h" #include "TH1.h" @@ -107,7 +107,7 @@ class Tracking float mMaxSnp{o2::base::Propagator::MAX_SIN_PHI}; ///< max snp when propagating tracks float mMaxStep{o2::base::Propagator::MAX_STEP}; ///< maximum step for propagation MatCorrType mMatCorr{MatCorrType::USEMatCorrNONE}; ///< if material correction should be done - RecoParam mRecoParam; ///< parameters required for TRD reconstruction + o2::gpu::GPUTRDRecoParam mRecoParam; ///< parameters required for TRD reconstruction bool mPID{true}; ///< if TPC only tracks are not available we don't fill PID info bool mApplyShift{true}; diff --git a/Detectors/TRD/qc/src/RawDataManager.cxx b/Detectors/TRD/qc/src/RawDataManager.cxx index 1add53cae12ac..c53cd434e6b7b 100644 --- a/Detectors/TRD/qc/src/RawDataManager.cxx +++ b/Detectors/TRD/qc/src/RawDataManager.cxx @@ -23,6 +23,8 @@ #include #include +#include +#include using namespace o2::trd; diff --git a/Detectors/TRD/qc/src/Tracking.cxx b/Detectors/TRD/qc/src/Tracking.cxx index 278ebe5391ff9..da2d05794e2d8 100644 --- a/Detectors/TRD/qc/src/Tracking.cxx +++ b/Detectors/TRD/qc/src/Tracking.cxx @@ -13,6 +13,7 @@ /// \brief Check the performance of the TRD in global tracking /// \author Ole Schmidt +#include "GPUO2InterfaceConfiguration.h" #include "TRDQC/Tracking.h" #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DetectorsBase/GeometryManager.h" @@ -25,7 +26,7 @@ using namespace o2::trd::constants; void Tracking::init() { - mRecoParam.setBfield(o2::base::Propagator::Instance()->getNominalBz()); + mRecoParam.init(o2::base::Propagator::Instance()->getNominalBz()); } void Tracking::setInput(const o2::globaltracking::RecoContainer& input) diff --git a/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFCoder.h b/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFCoder.h index 107a05a397c00..b34c2da395c5e 100644 --- a/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFCoder.h +++ b/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFCoder.h @@ -33,10 +33,10 @@ namespace o2 namespace trd { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TRD) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::TRD, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF @@ -259,8 +259,8 @@ o2::ctf::CTFIOSize CTFCoder::decode(const CTF::base& ec, VTRG& trigVec, VTRK& tr uint32_t firstEntryDig = digVec.size(); int16_t cid = 0; for (uint32_t id = 0; id < entriesDig[itrig]; id++) { - cid += CIDDig[digCount]; // 1st digit of trigger was encoded with abs CID, then increments - auto& dig = digVec.emplace_back(cid, ROBDig[digCount], MCMDig[digCount], chanDig[digCount]); + cid += CIDDig[digCount]; // as cid has phase, its stored fully not // 1st digit of trigger was encoded with abs CID, then increments + auto& dig = digVec.emplace_back(cid & 0xfff, ROBDig[digCount], MCMDig[digCount], chanDig[digCount], (cid >> 12) & 0x3); dig.setADC({&ADCDig[adcCount], constants::TIMEBINS}); digCount++; adcCount += constants::TIMEBINS; diff --git a/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFHelper.h b/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFHelper.h index 316f2c8a5c7f0..bb41ea9658c9d 100644 --- a/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFHelper.h +++ b/Detectors/TRD/reconstruction/include/TRDReconstruction/CTFHelper.h @@ -288,12 +288,12 @@ class CTFHelper // assume sorting in CID: for the 1st digit of the trigger return the abs CID, for the following ones: difference to previous CID value_type operator*() const { - return (*mTrigStart)[mIndex] ? mData[mIndex].getDetector() : mData[mIndex].getDetector() - mData[mIndex - 1].getDetector(); + return (*mTrigStart)[mIndex] ? mData[mIndex].getDetectorInFull() : mData[mIndex].getDetectorInFull() - mData[mIndex - 1].getDetectorInFull(); } value_type operator[](difference_type i) const { size_t id = mIndex + i; - return (*mTrigStart)[id] ? mData[id].getDetector() : mData[id].getDetector() - mData[id - 1].getDetector(); + return (*mTrigStart)[id] ? mData[id].getDetectorInFull() : mData[id].getDetectorInFull() - mData[id - 1].getDetectorInFull(); } }; diff --git a/Detectors/TRD/reconstruction/src/CruRawReader.cxx b/Detectors/TRD/reconstruction/src/CruRawReader.cxx index b4a37956759b9..05666691370db 100644 --- a/Detectors/TRD/reconstruction/src/CruRawReader.cxx +++ b/Detectors/TRD/reconstruction/src/CruRawReader.cxx @@ -301,6 +301,9 @@ bool CruRawReader::parseDigitHCHeaders(int hcid) DigitHCHeader1 header1; header1.word = headers[headerwordcount]; mPreTriggerPhase = header1.ptrigphase; + mPreTriggerPhase &= 0x0f; + mPreTriggerPhase /= 3; // remove the "gaps" in the pre trigger phase, so we dont have to sort it out later. + LOGP(debug, "Found pretrigger phase of Phase:{:x}", mPreTriggerPhase); headersfound.set(0); if ((header1.numtimebins > TIMEBINS) || (header1.numtimebins < 3) || mTimeBinsFixed && header1.numtimebins != mTimeBins) { @@ -802,6 +805,7 @@ int CruRawReader::parseDigitLinkData(int maxWords32, int hcid, int& wordsRejecte if (exitChannelLoop) { break; } + LOGP(debug, "Adding digit to event record det: {} rob: {} mcm: {} channel: {} Phase:{:x}", hcid / 2, (int)mcmHeader.rob, (int)mcmHeader.mcm, iChannel, mPreTriggerPhase); mEventRecords.getCurrentEventRecord().addDigit(Digit(hcid / 2, (int)mcmHeader.rob, (int)mcmHeader.mcm, iChannel, adcValues, mPreTriggerPhase)); ++mDigitsFound; } // end active channel diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/EntropyDecoderSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/EntropyDecoderSpec.h index 53c591e343134..9521d6262afbf 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/EntropyDecoderSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/EntropyDecoderSpec.h @@ -24,7 +24,7 @@ namespace trd { /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt = "none"); } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/EntropyEncoderSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/EntropyEncoderSpec.h index 673b600bee051..e31a629225f2c 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/EntropyEncoderSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/EntropyEncoderSpec.h @@ -24,7 +24,7 @@ namespace trd { /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h index 955af1995b0de..92c33d8c316b5 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/TRDGlobalTrackingSpec.h @@ -18,8 +18,9 @@ #include "Framework/Task.h" #include "TStopwatch.h" #include "TRDBase/GeometryFlat.h" -#include "GPUO2Interface.h" +#include "GPUO2ExternalUser.h" #include "GPUTRDTracker.h" +#include "GPUTRDRecoParam.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsTRD/TrackTRD.h" @@ -31,10 +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 "TRDBase/RecoParam.h" #include "DataFormatsTPC/TrackTPC.h" #include "DataFormatsITS/TrackITS.h" #include "DataFormatsITSMFT/TrkClusRef.h" @@ -52,12 +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); - } + 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; @@ -85,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 @@ -93,7 +88,7 @@ class TRDGlobalTracking : public o2::framework::Task // temporary members -> should go into processor (GPUTRDTracker or additional refit processor?) std::unique_ptr mTPCRefitter; ///< TPC refitter used for TPC tracks refit during the reconstruction const o2::tpc::ClusterNativeAccess* mTPCClusterIdxStruct = nullptr; ///< struct holding the TPC cluster indices - RecoParam mRecoParam; ///< parameters required for TRD reconstruction + o2::gpu::GPUTRDRecoParam mRecoParam; ///< parameters required for TRD reconstruction gsl::span mTrackletsRaw; ///< array of raw tracklets needed for TRD refit gsl::span mTrackletsCalib; ///< array of calibrated tracklets needed for TRD refit gsl::span mTPCTracksArray; ///< input TPC tracks used for refit @@ -110,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/include/TRDWorkflow/TRDPulseHeightSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/TRDPulseHeightSpec.h index 536353c7a9e90..be00e478608ec 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/TRDPulseHeightSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/TRDPulseHeightSpec.h @@ -58,6 +58,8 @@ class PuseHeightDevice : public o2::framework::Task if (mRunStopRequested) { std::vector mPHValues{}; // the calibration expects data at every TF, so inject dummy pc.outputs().snapshot(Output{"TRD", "PULSEHEIGHT", 0}, mPHValues); + std::vector mPHValuesHD{}; // the calibration expects data at every TF, so inject dummy + pc.outputs().snapshot(Output{"TRD", "PULSEHEIGHTHD", 0}, mPHValuesHD); return; } RecoContainer recoData; @@ -67,6 +69,7 @@ class PuseHeightDevice : public o2::framework::Task mPulseHeight->reset(); mPulseHeight->process(); pc.outputs().snapshot(Output{"TRD", "PULSEHEIGHT", 0}, mPulseHeight->getPHData()); + pc.outputs().snapshot(Output{"TRD", "PULSEHEIGHTHD", 0}, mPulseHeight->getPHDataHD()); if (pc.transitionState() == TransitionHandlingState::Requested) { LOG(info) << "Run stop requested, finalizing"; mRunStopRequested = true; @@ -101,6 +104,7 @@ DataProcessorSpec getTRDPulseHeightSpec(GID::mask_t src, bool digitsFromReader) std::vector outputs; outputs.emplace_back(o2::header::gDataOriginTRD, "PULSEHEIGHT", 0, Lifetime::Timeframe); + outputs.emplace_back(o2::header::gDataOriginTRD, "PULSEHEIGHTHD", 0, Lifetime::Timeframe); bool isTPCavailable = false; if (GID::includesSource(GID::Source::ITSTPC, src)) { diff --git a/Detectors/TRD/workflow/include/TRDWorkflow/VdAndExBCalibSpec.h b/Detectors/TRD/workflow/include/TRDWorkflow/VdAndExBCalibSpec.h index f45b7a1808287..e4d6a1641ed50 100644 --- a/Detectors/TRD/workflow/include/TRDWorkflow/VdAndExBCalibSpec.h +++ b/Detectors/TRD/workflow/include/TRDWorkflow/VdAndExBCalibSpec.h @@ -147,7 +147,7 @@ DataProcessorSpec getTRDVdAndExBCalibSpec() auto ccdbRequest = std::make_shared(true, // orbitResetTime true, // GRPECS=true false, // GRPLHCIF - false, // GRPMagField + true, // GRPMagField false, // askMatLUT o2::base::GRPGeomRequest::None, // geometry inputs); 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/EntropyDecoderSpec.cxx b/Detectors/TRD/workflow/src/EntropyDecoderSpec.cxx index b30732927c182..2caa4c370a021 100644 --- a/Detectors/TRD/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/TRD/workflow/src/EntropyDecoderSpec.cxx @@ -27,11 +27,10 @@ namespace o2 { namespace trd { - class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -44,7 +43,7 @@ class EntropyDecoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -109,7 +108,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"triggers"}, "TRD", "TRKTRGRD", 0, Lifetime::Timeframe}, @@ -119,19 +118,20 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_TRD", "TRD", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_TRD", "TRD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_TRD", "TRD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "trd-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"correct-trd-trigger-offset", VariantType::Bool, false, {"Correct decoded IR by TriggerOffsetsParam::LM_L0"}}, + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"correct-trd-trigger-offset", VariantType::Bool, false, {"Correct decoded IR by TriggerOffsetsParam::LM_L0"}}, {"bogus-trigger-rejection", VariantType::Int, 10, {">0 : discard, warn N times, <0 : warn only, =0: no check for triggers with no tracklets or bogus IR"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/src/EntropyEncoderSpec.cxx b/Detectors/TRD/workflow/src/EntropyEncoderSpec.cxx index d345dd74141ed..18b9a012db2f1 100644 --- a/Detectors/TRD/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/TRD/workflow/src/EntropyEncoderSpec.cxx @@ -27,11 +27,10 @@ namespace o2 { namespace trd { - class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -44,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task TStopwatch mTimer; }; -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -92,13 +91,16 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("triggers", "TRD", "TRKTRGRD", 0, Lifetime::Timeframe); inputs.emplace_back("tracklets", "TRD", "TRACKLETS", 0, Lifetime::Timeframe); inputs.emplace_back("digits", "TRD", "DIGITS", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "TRD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "TRD", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("TRD/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -107,14 +109,12 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"TRD", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "TRD", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}, {"bogus-trigger-check", VariantType::Int, 10, {"max bogus triggers to report, all if < 0"}}, {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace trd } // namespace o2 diff --git a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx index 598ce3c35c98c..40521c5fd5ee9 100644 --- a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx +++ b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx @@ -28,6 +28,7 @@ #include "GPUWorkflowHelper/GPUWorkflowHelper.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" +#include "Framework/DeviceSpec.h" #include "DataFormatsTPC/WorkflowHelper.h" #include "TPCReconstruction/TPCFastTransformHelperO2.h" #include "CommonConstants/GeomConstants.h" @@ -43,10 +44,11 @@ // GPU header #include "GPUReconstruction.h" #include "GPUChainTracking.h" +#include "GPUChainTrackingGetters.inc" #include "GPUO2InterfaceConfiguration.h" #include "GPUO2InterfaceUtils.h" #include "GPUSettings.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUTRDDef.h" #include "GPUTRDTrack.h" #include "GPUTRDTrackletWord.h" @@ -80,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(); } @@ -89,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 @@ -103,7 +108,7 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) mFlatGeo = std::make_unique(*geo); GPURecoStepConfiguration cfgRecoStep; - cfgRecoStep.steps = GPUDataTypes::RecoStep::NoRecoStep; + cfgRecoStep.steps = gpudatatypes::RecoStep::NoRecoStep; cfgRecoStep.inputs.clear(); cfgRecoStep.outputs.clear(); mRec = GPUReconstruction::CreateInstance("CPU", true); @@ -112,6 +117,8 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) config.ReadConfigurableParam(config); config.configGRP.solenoidBzNominalGPU = GPUO2InterfaceUtils::getNominalGPUBz(*o2::base::GRPGeomHelper::instance().getGRPMagField()); config.configProcessing.o2PropagatorUseGPUField = false; + mRecoParam.init(o2::base::Propagator::Instance()->getNominalBz(), &config.configReconstruction); + mRec->SetSettings(&config.configGRP, &config.configReconstruction, &config.configProcessing, &cfgRecoStep); mChainTracking = mRec->AddChain(); @@ -127,12 +134,11 @@ void TRDGlobalTracking::updateTimeDependentParams(ProcessingContext& pc) mRec->RegisterGPUProcessor(mTracker, false); mChainTracking->SetTRDGeometry(std::move(mFlatGeo)); + mChainTracking->SetTRDRecoParam(&mRecoParam); if (mRec->Init()) { LOG(fatal) << "GPUReconstruction could not be initialized"; } - mRecoParam.setBfield(o2::base::Propagator::Instance()->getNominalBz()); - mTracker->PrintSettings(); LOG(info) << "Strict matching mode is " << ((mStrict) ? "ON" : "OFF"); LOGF(info, "The search road in time for ITS-TPC tracks is set to %.1f sigma and %.2f us are added to it on top", @@ -145,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; } @@ -170,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()); } } @@ -186,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; @@ -290,8 +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->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 + 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(); @@ -552,6 +545,14 @@ void TRDGlobalTracking::run(ProcessingContext& pc) } } + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "GPU_rec_trd"), "GPU_rec_trd"); + } + } + mTimer.Stop(); } @@ -852,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); @@ -889,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) { @@ -952,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/entropy-encoder-workflow.cxx b/Detectors/TRD/workflow/src/entropy-encoder-workflow.cxx index 83fff5bceedef..dff3831cd78a5 100644 --- a/Detectors/TRD/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/TRD/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -37,6 +38,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) WorkflowSpec wf; // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); - wf.emplace_back(o2::trd::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"))); + wf.emplace_back(o2::trd::getEntropyEncoderSpec(cfgc.options().get("select-ir-frames"), cfgc.options().get("ctf-dict"))); return wf; } 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 0c2bbe5e02a47..334bb13064783 100644 --- a/Detectors/Upgrades/ALICE3/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/CMakeLists.txt @@ -11,11 +11,13 @@ add_subdirectory(Passive) add_subdirectory(TRK) +add_subdirectory(GlobalReconstruction) add_subdirectory(ECal) +add_subdirectory(FD3) add_subdirectory(FT3) add_subdirectory(FCT) add_subdirectory(AOD) add_subdirectory(IOTOF) add_subdirectory(RICH) add_subdirectory(MID) -add_subdirectory(macros) \ No newline at end of file +add_subdirectory(macros) diff --git a/Detectors/Upgrades/ALICE3/ECal/CMakeLists.txt b/Detectors/Upgrades/ALICE3/ECal/CMakeLists.txt index 83838a01d13f1..cc0a7b0337619 100644 --- a/Detectors/Upgrades/ALICE3/ECal/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/ECal/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(reconstruction) +add_subdirectory(DataFormatsECal) \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/CMakeLists.txt b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/CMakeLists.txt new file mode 100644 index 0000000000000..3448d6b31029d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/CMakeLists.txt @@ -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. + +o2_add_library(DataFormatsECal + SOURCES src/Digit.cxx + SOURCES src/MCLabel.cxx + SOURCES src/Cluster.cxx + PUBLIC_LINK_LIBRARIES O2::CommonDataFormat + O2::SimulationDataFormat + AliceO2::InfoLogger) + +o2_target_root_dictionary(DataFormatsECal + HEADERS include/DataFormatsECal/Digit.h + HEADERS include/DataFormatsECal/MCLabel.h + HEADERS include/DataFormatsECal/Cluster.h) diff --git a/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Cluster.h b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Cluster.h new file mode 100644 index 0000000000000..4a34ef1679f26 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Cluster.h @@ -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 Cluster.h +/// \brief Definition of ECal cluster class +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_CLUSTER_H +#define ALICEO2_ECAL_CLUSTER_H +#include +#include +#include +#include + +namespace o2 +{ +namespace ecal +{ +class Cluster +{ + public: + Cluster() = default; + Cluster(const Cluster& clu) = default; + ~Cluster() = default; + + // setters + void addDigit(int digitIndex, int towerId, double energy); + void setNLM(int nMax) { mNLM = nMax; } + void setE(float energy) { mE = energy; } + void setX(float x) { mX = x; } + void setY(float y) { mY = y; } + void setZ(float z) { mZ = z; } + void setChi2(float chi2) { mChi2 = chi2; } + void setEdgeFlag(bool isEdge) { mEdge = isEdge; } + void addMcTrackID(int mcTrackID, float energy) { mMcTrackEnergy[mcTrackID] += energy; } + + // getters + const std::map& getMcTrackEnergy() { return mMcTrackEnergy; } + int getMultiplicity() const { return mDigitIndex.size(); } + int getDigitIndex(int i) const { return mDigitIndex[i]; } + int getDigitTowerId(int i) const { return mDigitTowerId[i]; } + float getDigitEnergy(int i) const { return mDigitEnergy[i]; } + float getNLM() const { return mNLM; } + float getTime() const { return mTime; } + float getE() const { return mE; } + float getX() const { return mX; } + float getY() const { return mY; } + float getZ() const { return mZ; } + float getR() const { return std::sqrt(mX * mX + mY * mY); } + float getTheta() const { return std::atan2(getR(), mZ); } + float getEta() const { return -std::log(std::tan(getTheta() / 2.)); } + float getPhi() const { return std::atan2(mY, mX); } + float getChi2() const { return mChi2; } + bool isAtTheEdge() const { return mEdge; } + int getMcTrackID() const; + TLorentzVector getMomentum() const; + + private: + std::vector mDigitIndex; // vector of digit indices in digits vector + std::vector mDigitTowerId; // vector of corresponding digit tower Ids + std::vector mDigitEnergy; // vector of corresponding digit energies + std::map mMcTrackEnergy; // MC track indices and corresponding energies + int mNLM = 0; // number of local maxima in the initial cluster + float mTime = 0; // cluster time + float mE = 0; // cluster energy + float mX = 0; // estimated x-coordinate + float mY = 0; // estimated y-ccordinate + float mZ = 0; // estimated z-ccordinate + float mChi2 = 0; // chi2 wrt EM shape + bool mEdge = 0; // set to true if one of cluster digits is at the chamber edge + ClassDefNV(Cluster, 1); +}; +} // namespace ecal +} // namespace o2 + +#endif // ALICEO2_ECAL_CLUSTER_H diff --git a/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Digit.h b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Digit.h new file mode 100644 index 0000000000000..cc46a64e2cac0 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/Digit.h @@ -0,0 +1,55 @@ +// 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 ECal digit class +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_DIGIT_H +#define ALICEO2_ECAL_DIGIT_H + +#include +#include + +namespace o2 +{ +namespace ecal +{ +class Digit : public o2::dataformats::TimeStamp +{ + public: + Digit() = default; + Digit(int tower, double amplitudeGeV, double time); + ~Digit() = default; + + // setters + void setTower(int tower) { mTower = tower; } + void setAmplitude(double amplitude) { mAmplitudeGeV = amplitude; } + void setEnergy(double energy) { mAmplitudeGeV = energy; } + void setLabel(int label) { mLabel = label; } + + // getters + int getTower() const { return mTower; } + double getAmplitude() const { return mAmplitudeGeV; } + double getEnergy() const { return mAmplitudeGeV; } + int getLabel() const { return mLabel; } + + private: + double mAmplitudeGeV = 0.; ///< Amplitude (GeV) + int32_t mTower = -1; ///< Tower index (absolute cell ID) + int32_t mLabel = -1; ///< Index of the corresponding entry/entries in the MC label array + ClassDefNV(Digit, 1); +}; + +} // namespace ecal +} // namespace o2 +#endif // ALICEO2_ECAL_DIGIT_H diff --git a/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/MCLabel.h b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/MCLabel.h new file mode 100644 index 0000000000000..762779977ca53 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/include/DataFormatsECal/MCLabel.h @@ -0,0 +1,41 @@ +// 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 MCLabel.h +/// \brief MCLabel class to store MC truth info for ECal +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_MCLABEL_H +#define ALICEO2_ECAL_MCLABEL_H + +#include + +namespace o2 +{ +namespace ecal +{ +class MCLabel : public o2::MCCompLabel +{ + public: + MCLabel() = default; + MCLabel(int trackID, int eventID, int srcID, bool fake, float edep) : o2::MCCompLabel(trackID, eventID, srcID, fake), mEdep(edep) {} + float getEdep() const { return mEdep; } + + private: + float mEdep = 0; // deposited energy + + ClassDefNV(MCLabel, 1); +}; +} // namespace ecal +} // namespace o2 + +#endif // ALICEO2_ECAL_MCLABEL_H diff --git a/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Cluster.cxx b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Cluster.cxx new file mode 100644 index 0000000000000..f792344f1f50f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Cluster.cxx @@ -0,0 +1,57 @@ +// 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 Cluster.cxx +/// \brief Implementation of ECal cluster class +/// +/// \author Evgeny Kryshen + +#include +#include +#include +#include + +using namespace o2::ecal; + +ClassImp(Cluster); + +//============================================================================== +void Cluster::addDigit(int digitIndex, int towerId, double energy) +{ + mE += energy; + mDigitIndex.push_back(digitIndex); + mDigitTowerId.push_back(towerId); + mDigitEnergy.push_back(energy); +} + +//============================================================================== +int Cluster::getMcTrackID() const +{ + float maxEnergy = 0; + int maxID = 0; + for (const auto& [mcTrackID, energy] : mMcTrackEnergy) { + if (energy > maxEnergy) { + maxEnergy = energy; + maxID = mcTrackID; + } + } + return maxID; +} + +//============================================================================== +TLorentzVector Cluster::getMomentum() const +{ + double r = std::sqrt(mX * mX + mY * mY + mZ * mZ); + if (r == 0) { + return TLorentzVector(); + } + return TLorentzVector(mE * mX / r, mE * mY / r, mE * mZ / r, mE); +} diff --git a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawWorkflow.h b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/DataFormatsECalLinkDef.h similarity index 52% rename from Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawWorkflow.h rename to Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/DataFormatsECalLinkDef.h index 3bbab66d16497..5b0190aa10d45 100644 --- a/Detectors/FIT/FDD/workflow/include/FDDWorkflow/RawWorkflow.h +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/DataFormatsECalLinkDef.h @@ -9,20 +9,17 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef O2_FDD_RAWWORKFLOW_H -#define O2_FDD_RAWWORKFLOW_H +#ifdef __CLING__ -/// @file RawWorkflow.h +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -#include "Framework/WorkflowSpec.h" - -namespace o2 -{ -namespace fdd -{ -framework::WorkflowSpec getFDDRawWorkflow(bool useProcess, - bool dumpProcessor, bool dumpReader, - bool disableRootOut); -} // namespace fdd -} // namespace o2 +#pragma link C++ class o2::ecal::Digit + ; +#pragma link C++ class o2::ecal::MCLabel + ; +#pragma link C++ class o2::ecal::Cluster + ; +#pragma link C++ class std::vector < o2::ecal::Digit> + ; +#pragma link C++ class std::vector < o2::ecal::Cluster> + ; +#include "SimulationDataFormat/MCTruthContainer.h" +#pragma link C++ class o2::dataformats::MCTruthContainer < o2::ecal::MCLabel> + ; #endif diff --git a/Detectors/Upgrades/ITS3/reconstruction/src/FastMultEstConfig.cxx b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Digit.cxx similarity index 62% rename from Detectors/Upgrades/ITS3/reconstruction/src/FastMultEstConfig.cxx rename to Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Digit.cxx index 4f3a8a44b0391..c339c112c6858 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/src/FastMultEstConfig.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/Digit.cxx @@ -9,14 +9,16 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "ITS3Reconstruction/FastMultEstConfig.h" -#include "TRandom.h" +/// \file Digit.cxx +/// \brief Implementation of ECal digit class +/// +/// \author Evgeny Kryshen -O2ParamImpl(o2::its::FastMultEstConfig); +#include -using namespace o2::its; +using namespace o2::ecal; -bool FastMultEstConfig::isPassingRandomRejection() const +Digit::Digit(int tower, double amplitudeGeV, double time) + : mTower(tower), mAmplitudeGeV(amplitudeGeV), o2::dataformats::TimeStamp(time) { - return (cutRandomFraction <= 0. || gRandom->Rndm() > cutRandomFraction) ? true : false; } diff --git a/Detectors/FIT/FDD/workflow/src/RawDataReaderSpec.cxx b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/MCLabel.cxx similarity index 73% rename from Detectors/FIT/FDD/workflow/src/RawDataReaderSpec.cxx rename to Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/MCLabel.cxx index 631655d3038ec..4dbd2711f1521 100644 --- a/Detectors/FIT/FDD/workflow/src/RawDataReaderSpec.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/DataFormatsECal/src/MCLabel.cxx @@ -9,16 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @file RawDataReaderSpec.cxx +/// \file MCLabel.cxx +/// \brief MCLabel class to store MC truth info for ECal +/// +/// \author Evgeny Kryshen -#include "FDDWorkflow/RawDataReaderSpec.h" +#include -using namespace o2::framework; - -namespace o2 -{ -namespace fdd -{ - -} // namespace fdd -} // namespace o2 +ClassImp(o2::ecal::MCLabel); diff --git a/Detectors/Upgrades/ALICE3/ECal/README.md b/Detectors/Upgrades/ALICE3/ECal/README.md index 288040fbd5fd9..ff58683646409 100644 --- a/Detectors/Upgrades/ALICE3/ECal/README.md +++ b/Detectors/Upgrades/ALICE3/ECal/README.md @@ -1,10 +1,10 @@ # ALICE 3 Electromagnetic Calorimenter -This is top page for the ECL detector documentation. +This is top page for the ECAL detector documentation. \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/ECal/base/CMakeLists.txt index 70017cc051e80..b0e1229662653 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/ECal/base/CMakeLists.txt @@ -10,10 +10,14 @@ # or submit itself to any jurisdiction. o2_add_library(ECalBase - SOURCES src/GeometryTGeo.cxx + SOURCES src/Geometry.cxx + src/GeometryTGeo.cxx src/ECalBaseParam.cxx + src/Hit.cxx PUBLIC_LINK_LIBRARIES O2::DetectorsBase) o2_target_root_dictionary(ECalBase - HEADERS include/ECalBase/GeometryTGeo.h - include/ECalBase/ECalBaseParam.h) \ No newline at end of file + HEADERS include/ECalBase/Geometry.h + include/ECalBase/GeometryTGeo.h + include/ECalBase/ECalBaseParam.h + include/ECalBase/Hit.h) \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/ECalBaseParam.h b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/ECalBaseParam.h index b8b7c75e2b7d0..aa0de4119914a 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/ECalBaseParam.h +++ b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/ECalBaseParam.h @@ -9,22 +9,45 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// \file ECalBaseParam.h +/// \brief Geometry parameters configurable via o2-sim --configKeyValues +/// +/// \author Evgeny Kryshen + #ifndef O2_ECAL_BASEPARAM_H #define O2_ECAL_BASEPARAM_H -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" +#include +#include namespace o2 { namespace ecal { struct ECalBaseParam : public o2::conf::ConfigurableParamHelper { - float rMin = 125.0; // cm - float rMax = 155.0; // cm - float zLength = 350.0; // cm - - bool enableFwdEndcap = true; + bool enableFwdEndcap = false; + // general ecal barrel settings + double rMin = 125; // cm + double rMax = 155; // cm + double zLength = 350; // cm + int nSuperModules = 4; + // crystal module specification + int nCrystalModulesZ = 31; + int nCrystalModulesPhi = 96; + double crystalAlphaDeg = 0.4; // degrees + double crystalModuleWidth = 1.9; // cm + double crystalModuleLength = 18; // cm + // sampling module specification + int nSamplingModulesZ = 56; + int nSamplingModulesPhi = 67; + double samplingAlphaDeg = 0.4; // degrees + double samplingModuleWidth = 2.7; // cm + double frontPlateThickness = 1.; // cm + double pbLayerThickness = 0.12; // cm + double scLayerThickness = 0.15; // cm + int nSamplingLayers = 80; + // margin in z between crystal modules and sampling modules + double marginCrystalToSampling = 0.1; // cm O2ParamDef(ECalBaseParam, "ECalBase"); }; @@ -32,4 +55,4 @@ struct ECalBaseParam : public o2::conf::ConfigurableParamHelper { } // namespace ecal } // end namespace o2 -#endif \ No newline at end of file +#endif // O2_ECAL_BASEPARAM_H diff --git a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Geometry.h b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Geometry.h new file mode 100644 index 0000000000000..a780e36f45938 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Geometry.h @@ -0,0 +1,100 @@ +// 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 Geometry.h +/// \brief Geometry helper class +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_GEOMETRY_H +#define ALICEO2_ECAL_GEOMETRY_H + +#include +#include +#include + +namespace o2 +{ +namespace ecal +{ +class Geometry +{ + public: + static Geometry& instance() + { + static Geometry sGeom; + return sGeom; + } + + int getNcols() const; + int getNrows() const; + std::pair getSectorChamber(int cellId) const; + std::pair getSectorChamber(int iphi, int iz) const; + + void fillFrontFaceCenterCoordinates(); + int getCellID(int moduleId, int sectorId, bool isCrystal); + void detIdToRelIndex(int cellId, int& chamber, int& sector, int& iphi, int& iz) const; + void detIdToGlobalPosition(int detId, double& x, double& y, double& z); + std::pair globalRowColFromIndex(int cellID) const; + bool isCrystal(int cellID); + int areNeighboursVertex(int detId1, int detId2) const; + + double getTanBeta(int i) { return mTanBeta[i]; } + double getFrontFaceZatMinR(int i) { return mFrontFaceZatMinR[i]; } + double getFrontFaceCenterR(int i) { return mFrontFaceCenterR[i]; } + double getFrontFaceCenterZ(int i) { return mFrontFaceCenterZ[i]; } + double getFrontFaceCenterSamplingPhi(int i) { return mFrontFaceCenterSamplingPhi[i]; } + double getFrontFaceCenterCrystalPhi(int i) { return mFrontFaceCenterCrystalPhi[i]; } + double getFrontFaceCenterTheta(int i) { return mFrontFaceCenterTheta[i]; } + double getRMin() { return mRMin; } + double getCrystalModW() { return mCrystalModW; } + double getSamplingModW() { return mSamplingModW; } + double getCrystalAlpha() { return mCrystalAlpha; } + double getSamplingAlpha() { return mSamplingAlpha; } + double getCrystalDeltaPhi() { return 2 * std::atan(mCrystalModW / 2 / mRMin); } + double getSamplingDeltaPhi() { return 2 * std::atan(mSamplingModW / 2 / mRMin); } + double getFrontFaceMaxEta(int i); + double getCrystalPhiMin(); + double getSamplingPhiMin(); + int getNModulesZ() { return mNModulesZ; } + bool isAtTheEdge(int cellId); + + private: + Geometry(); + Geometry(const Geometry&) = delete; + Geometry& operator=(const Geometry&) = delete; + ~Geometry() = default; + double mRMin{0.}; + int mNSuperModules{0}; + int mNCrystalModulesZ{0}; + int mNSamplingModulesZ{0}; + int mNCrystalModulesPhi{0}; + int mNSamplingModulesPhi{0}; + double mCrystalModW{0.}; + double mSamplingModW{0.}; + double mSamplingAlpha{0.}; + double mCrystalAlpha{0.}; + double mMarginCrystalToSampling{0.}; + int mNModulesZ{0}; + std::vector mFrontFaceZatMinR; + std::vector mFrontFaceCenterR; + std::vector mFrontFaceCenterZ; + std::vector mFrontFaceCenterSamplingPhi; + std::vector mFrontFaceCenterCrystalPhi; + std::vector mFrontFaceCenterTheta; + std::vector mTanBeta; + + ClassDefNV(Geometry, 1); +}; +} // namespace ecal +} // namespace o2 + +#endif // ALICEO2_ECAL_GEOMETRY_H diff --git a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/GeometryTGeo.h index 1cff6dd7d3313..6975a5378a72f 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/GeometryTGeo.h @@ -9,6 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// \file GeometryTGeo.h +/// \brief Class containing ECal volume naming patterns +/// +/// \author Evgeny Kryshen + #ifndef ALICEO2_ECAL_GEOMETRYTGEO_H #define ALICEO2_ECAL_GEOMETRYTGEO_H @@ -27,21 +32,25 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache static GeometryTGeo* Instance(); static const char* getECalVolPattern() { return sVolumeName.c_str(); } - static const char* getECalSensorPattern() { return sSensorName.c_str(); } + static const char* getECalSectorPattern() { return sSectorName.c_str(); } + static const char* getECalModulePattern() { return sModuleName.c_str(); } static const char* composeSymNameECal() { return Form("%s_%d", o2::detectors::DetID(o2::detectors::DetID::ECL).getName(), 0); } - static const char* composeSymNameSensor(); // A single sensor for the moment + static const char* composeSymNameSector(int s); + static const char* composeSymNameModule(int s, int m); protected: static std::string sVolumeName; - static std::string sSensorName; + static std::string sSectorName; + static std::string sModuleName; private: static std::unique_ptr sInstance; }; } // namespace ecal } // namespace o2 -#endif + +#endif // ALICEO2_ECAL_GEOMETRYTGEO_H diff --git a/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Hit.h b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Hit.h new file mode 100644 index 0000000000000..006b2df5949e6 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/base/include/ECalBase/Hit.h @@ -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 Hit.h +/// \brief MC hit class to store energy loss per cell and per superparent +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_HIT_H +#define ALICEO2_ECAL_HIT_H + +#include +#include + +namespace o2 +{ +namespace ecal +{ +class Hit : public o2::BasicXYZEHit +{ + public: + /// \brief Default constructor + Hit() = default; + + /// \brief Hit constructor + /// + /// Fully defining information of the ECAL point (position, momentum, energy, track, ...) + /// + /// \param trackID Index of the track, defined as parent track entering the ECAL + /// \param cellID ID of the detector cell + /// \param pos Position vector of the point + /// \param mom Momentum vector for the particle at the point + /// \param tof Time of the hit + /// \param eLoss Energy loss + Hit(int trackID, int cellID, const math_utils::Point3D& pos, + const math_utils::Vector3D& mom, float tof, float eLoss) + : o2::BasicXYZEHit(pos.X(), pos.Y(), pos.Z(), tof, eLoss, trackID, 0), + mPvector(mom), + mCellID(cellID) + { + } + + /// \brief Destructor + ~Hit() = default; + + /// \brief Check whether the points are from the same parent and in the same detector volume + /// \return True if points are the same (origin and detector), false otherwise + bool operator==(const Hit& rhs) const; + + /// \brief Sorting points according to parent particle and detector volume + /// \return True if this point is smaller, false otherwise + bool operator<(const Hit& rhs) const; + + /// \brief Get cell ID + /// \return cell ID + int GetCellID() const { return mCellID; } + + private: + math_utils::Vector3D mPvector; ///< Momentum vector + int32_t mCellID; ///< Cell ID (used instead of short detID) + ClassDefNV(Hit, 1); +}; + +} // namespace ecal +} // namespace o2 + +#ifdef USESHM +namespace std +{ +template <> +class allocator : public o2::utils::ShmAllocator +{ +}; +} // namespace std +#endif + +#endif // ALICEO2_ECAL_HIT_H diff --git a/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseLinkDef.h b/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseLinkDef.h index 3bf7ccd32460c..0f0c0637ce2c1 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseLinkDef.h @@ -15,8 +15,11 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class o2::ecal::Geometry + ; #pragma link C++ class o2::ecal::GeometryTGeo + #pragma link C++ class o2::ecal::ECalBaseParam + ; +#pragma link C++ class o2::ecal::Hit + ; #pragma link C++ class o2::conf::ConfigurableParamHelper < o2::ecal::ECalBaseParam> + ; +#pragma link C++ class std::vector < o2::ecal::Hit> + ; #endif \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseParam.cxx b/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseParam.cxx index 54eb2860526b3..6847f42e26346 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseParam.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/base/src/ECalBaseParam.cxx @@ -9,6 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "ECalBase/ECalBaseParam.h" +/// \file ECalBaseParam.cxx +/// \brief Geometry parameters configurable via o2-sim --configKeyValues +/// +/// \author Evgeny Kryshen -O2ParamImpl(o2::ecal::ECalBaseParam); \ No newline at end of file +#include + +O2ParamImpl(o2::ecal::ECalBaseParam); diff --git a/Detectors/Upgrades/ALICE3/ECal/base/src/Geometry.cxx b/Detectors/Upgrades/ALICE3/ECal/base/src/Geometry.cxx new file mode 100644 index 0000000000000..2d6bdf160f393 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/base/src/Geometry.cxx @@ -0,0 +1,278 @@ +// 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 Geometry.cxx +/// \brief Geometry helper class +/// +/// \author Evgeny Kryshen + +#include "TMath.h" +#include +#include +#include +#include +#include "CommonConstants/MathConstants.h" +using namespace o2::ecal; +using o2::constants::math::PIHalf; +using o2::constants::math::TwoPI; + +//============================================================================== +Geometry::Geometry() +{ + auto& pars = ECalBaseParam::Instance(); + pars.updateFromFile("o2sim_configuration.ini", "ECalBase"); + pars.printKeyValues(false, true, true, false); + mRMin = pars.rMin; + mNSuperModules = pars.nSuperModules; + mNCrystalModulesZ = pars.nCrystalModulesZ; + mNSamplingModulesZ = pars.nSamplingModulesZ; + mNCrystalModulesPhi = pars.nCrystalModulesPhi; + mNSamplingModulesPhi = pars.nSamplingModulesPhi; + mCrystalModW = pars.crystalModuleWidth; + mSamplingModW = pars.samplingModuleWidth; + mMarginCrystalToSampling = pars.marginCrystalToSampling; + mCrystalAlpha = pars.crystalAlphaDeg * TMath::DegToRad(); + mSamplingAlpha = pars.samplingAlphaDeg * TMath::DegToRad(); + mNModulesZ = 2 * mNSamplingModulesZ + 2 * mNCrystalModulesZ; + fillFrontFaceCenterCoordinates(); +} + +//============================================================================== +int Geometry::getNcols() const +{ + return mNModulesZ; +} + +//============================================================================== +int Geometry::getNrows() const +{ + return mNSuperModules * (mNCrystalModulesPhi > mNSamplingModulesPhi ? mNCrystalModulesPhi : mNSamplingModulesPhi); +} + +//============================================================================== +double Geometry::getCrystalPhiMin() +{ + double superModuleDeltaPhi = TwoPI / mNSuperModules; + double crystalDeltaPhi = getCrystalDeltaPhi(); + return (superModuleDeltaPhi - crystalDeltaPhi * mNCrystalModulesPhi) / 2.; +} + +//============================================================================== +double Geometry::getSamplingPhiMin() +{ + double superModuleDeltaPhi = TwoPI / mNSuperModules; + double samplingDeltaPhi = getSamplingDeltaPhi(); + return (superModuleDeltaPhi - samplingDeltaPhi * mNSamplingModulesPhi) / 2.; +} + +double Geometry::getFrontFaceMaxEta(int i) +{ + double theta = std::atan(mRMin / getFrontFaceZatMinR(i)); + return -std::log(std::tan(theta / 2.)); +} + +//============================================================================== +void Geometry::fillFrontFaceCenterCoordinates() +{ + if (mFrontFaceCenterR.size() > 0) { + return; + } + mFrontFaceCenterTheta.resize(mNCrystalModulesZ + mNSamplingModulesZ); + mFrontFaceZatMinR.resize(mNCrystalModulesZ + mNSamplingModulesZ); + mFrontFaceCenterR.resize(mNCrystalModulesZ + mNSamplingModulesZ); + mFrontFaceCenterZ.resize(mNCrystalModulesZ + mNSamplingModulesZ); + mTanBeta.resize(mNCrystalModulesZ + mNSamplingModulesZ); + mFrontFaceCenterSamplingPhi.resize(mNSuperModules * mNSamplingModulesPhi); + mFrontFaceCenterCrystalPhi.resize(mNSuperModules * mNCrystalModulesPhi); + + double superModuleDeltaPhi = TwoPI / mNSuperModules; + double crystalDeltaPhi = getCrystalDeltaPhi(); + double samplingDeltaPhi = getSamplingDeltaPhi(); + double crystalPhiMin = getCrystalPhiMin(); + double samplingPhiMin = getSamplingPhiMin(); + for (int ism = 0; ism < mNSuperModules; ism++) { + // crystal + for (int i = 0; i < mNCrystalModulesPhi; i++) { + double phi0 = superModuleDeltaPhi * ism + crystalPhiMin + crystalDeltaPhi * i; + mFrontFaceCenterCrystalPhi[ism * mNCrystalModulesPhi + i] = phi0; + } + // sampling + for (int i = 0; i < mNSamplingModulesPhi; i++) { + double phi0 = superModuleDeltaPhi * ism + samplingPhiMin + samplingDeltaPhi * i; + mFrontFaceCenterSamplingPhi[ism * mNSamplingModulesPhi + i] = phi0; + } + } + + double theta0 = PIHalf - mCrystalAlpha; + double zAtMinR = mCrystalModW * std::cos(mCrystalAlpha); + + for (int m = 0; m < mNCrystalModulesZ; m++) { + mTanBeta[m] = std::sin(theta0 - mCrystalAlpha) * mCrystalModW / 2 / mRMin; + ROOT::Math::Polar2DVector vMid21(mCrystalModW / 2., PIHalf + theta0); + ROOT::Math::XYPoint pAtMinR(zAtMinR, mRMin); + ROOT::Math::XYPoint pc = pAtMinR + vMid21; + mFrontFaceZatMinR[m] = zAtMinR; + mFrontFaceCenterZ[m] = pc.x(); + mFrontFaceCenterR[m] = pc.y(); + mFrontFaceCenterTheta[m] = theta0; + theta0 -= 2 * mCrystalAlpha; + zAtMinR += mCrystalModW * std::cos(mCrystalAlpha) / std::sin(theta0 + mCrystalAlpha); + } + + theta0 = mFrontFaceCenterTheta[mNCrystalModulesZ - 1] - mCrystalAlpha - mSamplingAlpha; + zAtMinR = mFrontFaceZatMinR[mNCrystalModulesZ - 1]; + zAtMinR += mSamplingModW * std::cos(mSamplingAlpha) / std::sin(theta0 + mSamplingAlpha); + zAtMinR += mMarginCrystalToSampling; + + for (int m = 0; m < mNSamplingModulesZ; m++) { + int i = m + mNCrystalModulesZ; + mTanBeta[i] = std::sin(theta0 - mSamplingAlpha) * mSamplingModW / 2 / mRMin; + ROOT::Math::Polar2DVector vMid21(mSamplingModW / 2., PIHalf + theta0); + ROOT::Math::XYPoint pAtMinR(zAtMinR, mRMin); + ROOT::Math::XYPoint pc = pAtMinR + vMid21; + mFrontFaceZatMinR[i] = zAtMinR; + mFrontFaceCenterZ[i] = pc.x(); + mFrontFaceCenterR[i] = pc.y(); + mFrontFaceCenterTheta[i] = theta0; + theta0 -= 2 * mSamplingAlpha; + zAtMinR += mSamplingModW * std::cos(mSamplingAlpha) / std::sin(theta0 + mSamplingAlpha); + } +} + +int Geometry::getCellID(int moduleId, int sectorId, bool isCrystal) +{ + int cellID = 0; + if (isCrystal) { + if (moduleId % 2 == 0) { // crystal at positive eta + cellID = sectorId * mNModulesZ + moduleId / 2 + mNSamplingModulesZ + mNCrystalModulesZ; + } else { // crystal at negative eta + cellID = sectorId * mNModulesZ - moduleId / 2 + mNSamplingModulesZ + mNCrystalModulesZ - 1; + } + } else { + if (sectorId % 2 == 0) { // sampling at positive eta + cellID = sectorId / 2 * mNModulesZ + moduleId + mNSamplingModulesZ + mNCrystalModulesZ * 2; + } else { // sampling at negative eta + cellID = sectorId / 2 * mNModulesZ - moduleId + mNSamplingModulesZ - 1; + } + } + return cellID; +} + +//============================================================================== +std::pair Geometry::globalRowColFromIndex(int cellID) const +{ + int ip = cellID / mNModulesZ; // row + int iz = cellID % mNModulesZ; // col + return {ip, iz}; +} + +//============================================================================== +bool Geometry::isCrystal(int cellID) +{ + auto [row, col] = globalRowColFromIndex(cellID); + return (col >= mNSamplingModulesZ && col < mNSamplingModulesZ + 2 * mNCrystalModulesZ); +} + +//============================================================================== +std::pair Geometry::getSectorChamber(int cellId) const +{ + int iphi = cellId / mNModulesZ; + int iz = cellId % mNModulesZ; + return getSectorChamber(iphi, iz); +} + +//============================================================================== +std::pair Geometry::getSectorChamber(int iphi, int iz) const +{ + int chamber = iz < mNSamplingModulesZ ? 0 : (iz < mNSamplingModulesZ + 2 * mNCrystalModulesZ ? 1 : 2); + int sector = iphi / (chamber == 1 ? mNCrystalModulesPhi : mNSamplingModulesPhi); + return {sector, chamber}; +} + +//============================================================================== +void Geometry::detIdToRelIndex(int cellId, int& chamber, int& sector, int& iphi, int& iz) const +{ + // 3 chambers - sampling/crystal/sampling + iphi = cellId / mNModulesZ; + iz = cellId % mNModulesZ; + auto pair = getSectorChamber(iphi, iz); + sector = pair.first; + chamber = pair.second; +} + +//============================================================================== +void Geometry::detIdToGlobalPosition(int detId, double& x, double& y, double& z) +{ + int chamber, sector, iphi, iz; + detIdToRelIndex(detId, chamber, sector, iphi, iz); + double r = 0; + if (iz < mNSamplingModulesZ + mNCrystalModulesZ) { + z = -mFrontFaceCenterZ[mNSamplingModulesZ + mNCrystalModulesZ - iz - 1]; + r = mFrontFaceCenterR[mNSamplingModulesZ + mNCrystalModulesZ - iz - 1]; + } else { + z = mFrontFaceCenterZ[iz % (mNSamplingModulesZ + mNCrystalModulesZ)]; + r = mFrontFaceCenterR[iz % (mNSamplingModulesZ + mNCrystalModulesZ)]; + } + double phi = chamber == 1 ? mFrontFaceCenterCrystalPhi[iphi] : mFrontFaceCenterSamplingPhi[iphi]; + x = r * std::cos(phi); + y = r * std::sin(phi); +} + +//============================================================================== +int Geometry::areNeighboursVertex(int detId1, int detId2) const +{ + int ch1, sector1, iphi1, iz1; + int ch2, sector2, iphi2, iz2; + detIdToRelIndex(detId1, ch1, sector1, iphi1, iz1); + detIdToRelIndex(detId2, ch2, sector2, iphi2, iz2); + if (sector1 != sector2 || ch1 != ch2) { + return 0; + } + if (std::abs(iphi1 - iphi2) <= 1 && std::abs(iz1 - iz2) <= 1) { + return 1; + } + return 0; +} + +//============================================================================== +bool Geometry::isAtTheEdge(int cellId) +{ + auto [row, col] = globalRowColFromIndex(cellId); + if (col == 0) { + return 1; + } else if (col == mNSamplingModulesZ) { + return 1; + } else if (col == mNSamplingModulesZ - 1) { + return 1; + } else if (col == mNSamplingModulesZ + 2 * mNCrystalModulesZ) { + return 1; + } else if (col == mNSamplingModulesZ + 2 * mNCrystalModulesZ - 1) { + return 1; + } else if (col == mNModulesZ - 1) { + return 1; + } + for (int m = 0; m <= mNSuperModules; m++) { + if (isCrystal(cellId)) { + if (row == m * mNCrystalModulesPhi) { + return 1; + } else if (row == m * mNCrystalModulesPhi - 1) { + return 1; + } + } else { + if (row == m * mNSamplingModulesPhi) { + return 1; + } else if (row == m * mNSamplingModulesPhi - 1) { + return 1; + } + } + } + return 0; +} diff --git a/Detectors/Upgrades/ALICE3/ECal/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/ECal/base/src/GeometryTGeo.cxx index 49f57d8a8c5cc..aca4f5548dc51 100644 --- a/Detectors/Upgrades/ALICE3/ECal/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/base/src/GeometryTGeo.cxx @@ -9,6 +9,11 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +/// \file GeometryTGeo.cxx +/// \brief Class containing ECal volume naming patterns +/// +/// \author Evgeny Kryshen + #include #include @@ -19,7 +24,8 @@ namespace ecal std::unique_ptr GeometryTGeo::sInstance; std::string GeometryTGeo::sVolumeName = "ECALV"; -std::string GeometryTGeo::sSensorName = "ECALSensor"; +std::string GeometryTGeo::sSectorName = "ECALSector"; +std::string GeometryTGeo::sModuleName = "ECALModule"; GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache() { @@ -57,10 +63,15 @@ GeometryTGeo* GeometryTGeo::Instance() return sInstance.get(); } -const char* GeometryTGeo::composeSymNameSensor() +const char* GeometryTGeo::composeSymNameSector(int s) +{ + return Form("%s/%s_%d", composeSymNameECal(), getECalSectorPattern(), s); +} + +const char* GeometryTGeo::composeSymNameModule(int s, int m) { - return Form("%s/%d", composeSymNameECal(), 0); + return Form("%s/%s_%d", composeSymNameSector(s), getECalModulePattern(), m); } } // namespace ecal -} // namespace o2 \ No newline at end of file +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/ECal/base/src/Hit.cxx b/Detectors/Upgrades/ALICE3/ECal/base/src/Hit.cxx new file mode 100644 index 0000000000000..ee2034314d2d8 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/base/src/Hit.cxx @@ -0,0 +1,34 @@ +// 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 Hit.cxx +/// \brief MC hit class to store energy loss per cell and per superparent +/// +/// \author Evgeny Kryshen + +#include + +ClassImp(o2::ecal::Hit); + +using namespace o2::ecal; + +bool Hit::operator<(const Hit& rhs) const +{ + if (GetTrackID() != rhs.GetTrackID()) { + return GetTrackID() < rhs.GetTrackID(); + } + return GetCellID() < rhs.GetCellID(); +} + +bool Hit::operator==(const Hit& rhs) const +{ + return (GetCellID() == rhs.GetCellID()) && (GetTrackID() == rhs.GetTrackID()); +} diff --git a/Detectors/Upgrades/ALICE3/ECal/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/ECal/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..f51a9c067d6b3 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/reconstruction/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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(ECalReconstruction + SOURCES src/Clusterizer.cxx + PUBLIC_LINK_LIBRARIES O2::ECalBase + O2::DataFormatsECal + AliceO2::InfoLogger) + +o2_target_root_dictionary(ECalReconstruction + HEADERS include/ECalReconstruction/Clusterizer.h) diff --git a/Detectors/Upgrades/ALICE3/ECal/reconstruction/include/ECalReconstruction/Clusterizer.h b/Detectors/Upgrades/ALICE3/ECal/reconstruction/include/ECalReconstruction/Clusterizer.h new file mode 100644 index 0000000000000..5e4d36f831360 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/reconstruction/include/ECalReconstruction/Clusterizer.h @@ -0,0 +1,83 @@ +// 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 Clusterizer.h +/// \brief Class for cluster finding and unfolding +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_CLUSTERIZER_H +#define ALICEO2_ECAL_CLUSTERIZER_H + +#include +#include +#include +#include + +using o2::ecal::Cluster; +using o2::ecal::Digit; +class TF1; + +namespace o2 +{ +namespace ecal +{ +class Clusterizer +{ + public: + Clusterizer(bool applyCorrectionZ = 1, bool applyCorrectionE = 1); + ~Clusterizer() = default; + void initialize() {}; + void addDigitToCluster(Cluster& cluster, int row, int col, const gsl::span& digits); + void findClusters(const gsl::span& digits, std::vector& foundClusters, std::vector& unfoldedClusters); + void makeClusters(const gsl::span& digits, std::vector& clusters); + void makeUnfoldings(std::vector& foundClusters, std::vector& unfoldedClusters); + void unfoldOneCluster(Cluster* iniClu, int nMax, int* digitId, float* maxAtEnergy, std::vector& unfoldedClusters); + void evalClusters(std::vector& clusters); + int getNumberOfLocalMax(Cluster& clu, int* maxAt, float* maxAtEnergy); + double showerShape(double dx, double dz, bool isCrystal); + void setLogWeight(double logWeight) { mLogWeight = logWeight; } + void setClusteringThreshold(double threshold) { mClusteringThreshold = threshold; } + void setCrystalDigitThreshold(double threshold) { mCrystalDigitThreshold = threshold; } + void setSamplingDigitThreshold(double threshold) { mSamplingDigitThreshold = threshold; } + void setCrystalEnergyCorrectionPars(std::vector pars) { mCrystalEnergyCorrectionPars = pars; } + void setSamplingEnergyCorrectionPars(std::vector pars) { mSamplingEnergyCorrectionPars = pars; } + void setCrystalZCorrectionPars(std::vector pars) { mCrystalZCorrectionPars = pars; } + void setSamplingZCorrectionPars(std::vector pars) { mSamplingZCorrectionPars = pars; } + + private: + std::vector> mDigitIndices; // 2D map of digit indices used for recursive cluster finding + bool mUnfoldClusters = true; // to perform cluster unfolding + double mCrystalDigitThreshold = 0.040; // minimal energy of crystal digit + double mSamplingDigitThreshold = 0.100; // minimal energy of sampling digit + double mClusteringThreshold = 0.050; // minimal energy of digit to start clustering (GeV) + double mClusteringTimeGate = 1e9; // maximal time difference between digits to be accepted to clusters (in ns) + int mNLMMax = 30; // maximal number of local maxima in unfolding + double mLogWeight = 4.; // cutoff used in log. weight calculation + double mUnfogingEAccuracy = 1.e-4; // accuracy of energy calculation in unfoding prosedure (GeV) + double mUnfogingXZAccuracy = 1.e-2; // accuracy of position calculation in unfolding procedure (cm) + int mNMaxIterations = 100; // maximal number of iterations in unfolding procedure + double mLocalMaximumCut = 0.015; // minimal height of local maximum over neighbours + bool mApplyCorrectionZ = 1; // apply z-correction + bool mApplyCorrectionE = 1; // apply energy-correction + TF1* fCrystalShowerShape; //! Crystal shower shape + TF1* fSamplingShowerShape; //! Sampling shower shape + TF1* fCrystalRMS; //! Crystal RMS + std::vector mCrystalEnergyCorrectionPars; // crystal energy-correction parameters + std::vector mSamplingEnergyCorrectionPars; // sampling energy-correction parameters + std::vector mCrystalZCorrectionPars; // crystal z-correction parameters + std::vector mSamplingZCorrectionPars; // sampling z-correction parameters +}; + +} // namespace ecal +} // namespace o2 + +#endif // ALICEO2_ECAL_CLUSTERIZER_H diff --git a/Detectors/Upgrades/ALICE3/ECal/reconstruction/src/Clusterizer.cxx b/Detectors/Upgrades/ALICE3/ECal/reconstruction/src/Clusterizer.cxx new file mode 100644 index 0000000000000..28efa78059dc1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/reconstruction/src/Clusterizer.cxx @@ -0,0 +1,494 @@ +// 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 Clusterizer.cxx +/// \brief Class for cluster finding and unfolding +/// +/// \author Evgeny Kryshen + +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::ecal; + +//============================================================================== +Clusterizer::Clusterizer(bool applyCorrectionZ, bool applyCorrectionE) +{ + auto& geo = Geometry::instance(); + mDigitIndices.resize(geo.getNrows(), std::vector(geo.getNcols(), -1)); + mApplyCorrectionZ = applyCorrectionZ; + mApplyCorrectionE = applyCorrectionE; + + mCrystalEnergyCorrectionPars.reserve(6); + mCrystalEnergyCorrectionPars[0] = 0.00444; + mCrystalEnergyCorrectionPars[1] = -1.322; + mCrystalEnergyCorrectionPars[2] = 1.021; + mCrystalEnergyCorrectionPars[3] = 0.0018; + mCrystalEnergyCorrectionPars[4] = 0.; + mCrystalEnergyCorrectionPars[5] = 0.; + + mSamplingEnergyCorrectionPars.reserve(6); + mSamplingEnergyCorrectionPars[0] = 0.0033; + mSamplingEnergyCorrectionPars[1] = -2.09; + mSamplingEnergyCorrectionPars[2] = 1.007; + mSamplingEnergyCorrectionPars[3] = 0.0667; + mSamplingEnergyCorrectionPars[4] = -0.108; + mSamplingEnergyCorrectionPars[5] = 0.0566; + + mCrystalZCorrectionPars.reserve(9); + mCrystalZCorrectionPars[0] = -0.005187; + mCrystalZCorrectionPars[1] = 0.7301; + mCrystalZCorrectionPars[2] = -0.7382; + mCrystalZCorrectionPars[3] = 0.; + mCrystalZCorrectionPars[4] = 0.; + mCrystalZCorrectionPars[5] = 0.; + mCrystalZCorrectionPars[6] = 0.; + mCrystalZCorrectionPars[7] = 0.; + mCrystalZCorrectionPars[8] = 0.; + + mSamplingZCorrectionPars.reserve(9); + mSamplingZCorrectionPars[0] = -2.137; + mSamplingZCorrectionPars[1] = 6.400; + mSamplingZCorrectionPars[2] = -3.342; + mSamplingZCorrectionPars[3] = -0.1364; + mSamplingZCorrectionPars[4] = 0.4019; + mSamplingZCorrectionPars[5] = -0.1969; + mSamplingZCorrectionPars[6] = 0.008223; + mSamplingZCorrectionPars[7] = -0.02425; + mSamplingZCorrectionPars[8] = 0.01190; + + fCrystalShowerShape = new TF1("fCrystal", "x<[1] ? [0]*exp([3]*x+[4]*x*x+[5]*x*x*x) : (x<[2] ? [0]*[6]*exp([7]*x+[8]*x*x) : [0]*[9]*exp([10]*x+[11]*x*x))", 0, 15); + double pc[12]; + pc[0] = 1. / 13.15; + pc[1] = 2.2; + pc[2] = 5; + pc[3] = 4.38969; + pc[4] = -5.15975; + pc[5] = 1.18978; + pc[6] = 1.48726; + pc[7] = -1.54621; + pc[8] = 0.0814617; + pc[9] = 0.0369055; + pc[10] = -0.174372; + pc[11] = -0.0455978; + + fCrystalShowerShape->SetParameters(pc); + + fSamplingShowerShape = new TF1("fSampling", "x<[1] ? [0]*exp([3]*x+[4]*x*x+[5]*x*x*x) : (x<[2] ? [0]*[6]*exp([7]*x+[8]*x*x) : [0]*[9]*exp([10]*x+[11]*x*x))", 0, 15); + double ps[12]; + ps[0] = 1 / 35.6; + ps[1] = 3.2; + ps[2] = 6; + ps[3] = 3.06543; + ps[4] = -2.23235; + ps[5] = 0.325344; + ps[6] = 6.0733; + ps[7] = -1.62713; + ps[8] = 0.0965569; + ps[9] = 0.0765706; + ps[10] = -0.217398; + ps[11] = -0.0204646; + fSamplingShowerShape->SetParameters(ps); + + fCrystalRMS = new TF1("fCrystalRMS", "[0]*x*exp([1]*x+[2]*x*x+[3]*x*x*x)", 0, 2.2); + double p[4]; + p[0] = 1.39814; + p[1] = -6.05426; + p[2] = 6.26678; + p[3] = -1.97092; + fCrystalRMS->SetParameters(p); +} + +//============================================================================== +void Clusterizer::findClusters(const gsl::span& digits, std::vector& foundClusters, std::vector& unfoldedClusters) +{ + foundClusters.clear(); + unfoldedClusters.clear(); + + // Collect list of clusters + makeClusters(digits, foundClusters); + + // Split clusters with several local maxima if necessary + makeUnfoldings(foundClusters, unfoldedClusters); + + // Evaluate cluster position, dispersion etc. + evalClusters(foundClusters); + evalClusters(unfoldedClusters); +} + +//============================================================================== +void Clusterizer::addDigitToCluster(Cluster& cluster, int row, int col, const gsl::span& digits) +{ + auto& geo = Geometry::instance(); + if (row < 0 || row >= geo.getNrows() || col < 0 || col >= geo.getNcols()) { + return; + } + int digitIndex = mDigitIndices[row][col]; + LOGP(debug, " checking row={} and col={} digitIndex={}", row, col, digitIndex); + if (digitIndex < 0) { + return; + } + const Digit& digit = digits[digitIndex]; + if (cluster.getMultiplicity() > 0) { + // check if new digit is in the same chamber and sector + const Digit& digit2 = digits[cluster.getDigitIndex(0)]; + auto [sector1, ch1] = geo.getSectorChamber(digit.getTower()); + auto [sector2, ch2] = geo.getSectorChamber(digit2.getTower()); + LOGP(debug, " checking if sector and chamber are the same: ({},{}) ({},{})", sector1, ch1, sector2, ch2); + if (sector1 != sector2 || ch1 != ch2) { + return; + } + } + + mDigitIndices[row][col] = -1; + cluster.addDigit(digitIndex, digit.getTower(), digit.getEnergy()); + LOGP(debug, " adding new digit at row={} and col={}", row, col); + addDigitToCluster(cluster, row - 1, col, digits); + addDigitToCluster(cluster, row + 1, col, digits); + addDigitToCluster(cluster, row, col - 1, digits); + addDigitToCluster(cluster, row, col + 1, digits); +} + +//============================================================================== +void Clusterizer::makeClusters(const gsl::span& digits, std::vector& clusters) +{ + // Combine digits into cluster + + int nDigits = digits.size(); + + // reset mDigitIndices + for (auto& rows : mDigitIndices) { + rows.assign(rows.size(), -1); + } + + // fill mDigitIndices + auto& geo = Geometry::instance(); + for (int i = 0; i < nDigits; i++) { + const Digit& digit = digits[i]; + auto [row, col] = geo.globalRowColFromIndex(digit.getTower()); + bool isCrystal = geo.isCrystal(digit.getTower()); + if (isCrystal) { + if (digit.getEnergy() < mCrystalDigitThreshold) { + continue; + } + } else { + if (digit.getEnergy() < mSamplingDigitThreshold) { + continue; + } + } + mDigitIndices[row][col] = i; + } + + // add digit seeds to clusters and recursively add neighbours + for (int i = 0; i < nDigits; i++) { + const Digit& digitSeed = digits[i]; + auto [row, col] = geo.globalRowColFromIndex(digitSeed.getTower()); + if (mDigitIndices[row][col] < 0) { + continue; // digit was already added in one of the clusters + } + if (digitSeed.getEnergy() < mClusteringThreshold) { + continue; + } + LOGP(debug, " starting new cluster at row={} and col={}", row, col); + auto& cluster = clusters.emplace_back(); + addDigitToCluster(cluster, row, col, digits); + } + + LOGP(debug, "made {} clusters from {} digits", clusters.size(), nDigits); +} + +//============================================================================== +void Clusterizer::makeUnfoldings(std::vector& foundClusters, std::vector& unfoldedClusters) +{ + // Split cluster if several local maxima are found + if (!mUnfoldClusters) { + return; + } + + int* maxAt = new int[mNLMMax]; + float* maxAtEnergy = new float[mNLMMax]; + + for (auto& clu : foundClusters) { + int nMax = getNumberOfLocalMax(clu, maxAt, maxAtEnergy); + if (nMax > 1) { + unfoldOneCluster(&clu, nMax, maxAt, maxAtEnergy, unfoldedClusters); + } else { + clu.setNLM(1); + unfoldedClusters.emplace_back(clu); + } + } + delete[] maxAt; + delete[] maxAtEnergy; +} + +//============================================================================== +void Clusterizer::unfoldOneCluster(Cluster* iniClu, int nMax, int* digitId, float* maxAtEnergy, std::vector& unfoldedClusters) +{ + // Based on MpdEmcClusterizerKI::UnfoldOneCluster by D. Peresunko + // Performs the unfolding of a cluster with nMax overlapping showers + // Parameters: iniClu cluster to be unfolded + // nMax number of local maxima found (this is the number of new clusters) + // digitId: index of digits, corresponding to local maxima + // maxAtEnergy: energies of digits, corresponding to local maxima + + // Take initial cluster and calculate local coordinates of digits + // To avoid multiple re-calculation of same parameters + int mult = iniClu->getMultiplicity(); + std::vector x(mult); + std::vector y(mult); + std::vector z(mult); + std::vector e(mult); + std::vector> eInClusters(mult, std::vector(nMax)); + + auto& geo = Geometry::instance(); + bool isCrystal = geo.isCrystal(iniClu->getDigitTowerId(0)); + + for (int idig = 0; idig < mult; idig++) { + e[idig] = iniClu->getDigitEnergy(idig); + geo.detIdToGlobalPosition(iniClu->getDigitTowerId(idig), x[idig], y[idig], z[idig]); + } + + // Coordinates of centers of clusters + std::vector xMax(nMax); + std::vector yMax(nMax); + std::vector zMax(nMax); + std::vector eMax(nMax); + + for (int iclu = 0; iclu < nMax; iclu++) { + xMax[iclu] = x[digitId[iclu]]; + yMax[iclu] = y[digitId[iclu]]; + zMax[iclu] = z[digitId[iclu]]; + eMax[iclu] = e[digitId[iclu]]; + } + + std::vector prop(nMax); // proportion of clusters in the current digit + + // Try to decompose cluster to contributions + int nIterations = 0; + bool insuficientAccuracy = true; + + while (insuficientAccuracy && nIterations < mNMaxIterations) { + // Loop over all digits of parent cluster and split their energies between daughter clusters + // according to shower shape + for (int idig = 0; idig < mult; idig++) { + double eEstimated = 0; + for (int iclu = 0; iclu < nMax; iclu++) { + prop[iclu] = eMax[iclu] * showerShape(std::sqrt((x[idig] - xMax[iclu]) * (x[idig] - xMax[iclu]) + + (y[idig] - yMax[iclu]) * (y[idig] - yMax[iclu])), + z[idig] - zMax[iclu], isCrystal); + eEstimated += prop[iclu]; + } + if (eEstimated == 0.) { // numerical accuracy + continue; + } + // Split energy of digit according to contributions + for (int iclu = 0; iclu < nMax; iclu++) { + eInClusters[idig][iclu] = e[idig] * prop[iclu] / eEstimated; + } + } + // Recalculate parameters of clusters and check relative variation of energy and absolute of position + insuficientAccuracy = false; // will be true if at least one parameter changed too much + for (int iclu = 0; iclu < nMax; iclu++) { + double oldX = xMax[iclu]; + double oldY = yMax[iclu]; + double oldZ = zMax[iclu]; + double oldE = eMax[iclu]; + // new energy, need for weight + eMax[iclu] = 0; + for (int idig = 0; idig < mult; idig++) { + eMax[iclu] += eInClusters[idig][iclu]; + } + xMax[iclu] = 0; + yMax[iclu] = 0; + zMax[iclu] = 0; + double wtot = 0.; + for (int idig = 0; idig < mult; idig++) { + double w = std::max(std::log(eInClusters[idig][iclu] / eMax[iclu]) + mLogWeight, 0.); + xMax[iclu] += x[idig] * w; + yMax[iclu] += y[idig] * w; + zMax[iclu] += z[idig] * w; + wtot += w; + } + if (wtot > 0.) { + xMax[iclu] /= wtot; + yMax[iclu] /= wtot; + zMax[iclu] /= wtot; + } + // Compare variation of parameters + insuficientAccuracy += (std::abs(eMax[iclu] - oldE) > mUnfogingEAccuracy); + insuficientAccuracy += (std::abs(xMax[iclu] - oldX) > mUnfogingXZAccuracy); + insuficientAccuracy += (std::abs(yMax[iclu] - oldY) > mUnfogingXZAccuracy); + insuficientAccuracy += (std::abs(zMax[iclu] - oldZ) > mUnfogingXZAccuracy); + } + nIterations++; + } + + // Iterations finished, add new clusters + for (int iclu = 0; iclu < nMax; iclu++) { + auto& clu = unfoldedClusters.emplace_back(); + clu.setNLM(nMax); + for (int idig = 0; idig < mult; idig++) { + int jdigit = iniClu->getDigitIndex(idig); + int towerId = iniClu->getDigitTowerId(idig); + clu.addDigit(jdigit, towerId, eInClusters[idig][iclu]); + } + } +} + +//============================================================================== +void Clusterizer::evalClusters(std::vector& clusters) +{ + auto& geo = Geometry::instance(); + for (auto& cluster : clusters) { + double x = 0; + double y = 0; + double z = 0; + double wtot = 0; + double etot = cluster.getE(); + for (size_t i = 0; i < cluster.getMultiplicity(); i++) { + float energy = cluster.getDigitEnergy(i); + int towerId = cluster.getDigitTowerId(i); + double xi, yi, zi; + geo.detIdToGlobalPosition(towerId, xi, yi, zi); + double w = std::max(0., mLogWeight + std::log(energy / etot)); + x += w * xi; + y += w * yi; + z += w * zi; + wtot += w; + } + if (wtot != 0) { + x /= wtot; + y /= wtot; + z /= wtot; + } + cluster.setX(x); + cluster.setY(y); + cluster.setZ(z); + + // cluster shape + float chi2 = 0; + int ndf = 0; + float ee = cluster.getE(); + for (size_t i = 0; i < cluster.getMultiplicity(); i++) { + float energy = cluster.getDigitEnergy(i); + int towerId = cluster.getDigitTowerId(i); + double xi, yi, zi; + geo.detIdToGlobalPosition(towerId, xi, yi, zi); + double r = std::sqrt((x - xi) * (x - xi) + (y - yi) * (y - yi) + (z - zi) * (z - zi)); + if (r > 2.2) { + continue; + } + double frac = fCrystalShowerShape->Eval(r); + double rms = fCrystalRMS->Eval(r); + chi2 += std::pow((energy / ee - frac) / rms, 2.); + ndf++; + } + cluster.setChi2(chi2 / ndf); + + // correct cluster energy and z position + float eta = std::abs(cluster.getEta()); + bool isCrystal = geo.isCrystal(cluster.getDigitTowerId(0)); + if (mApplyCorrectionE) { + std::vector& pe = isCrystal ? mCrystalEnergyCorrectionPars : mSamplingEnergyCorrectionPars; + ee *= pe[0] * std::pow(ee, pe[1]) + pe[2] + pe[3] * eta + pe[4] * eta * eta + pe[5] * eta * eta * eta; + cluster.setE(ee); + } + if (mApplyCorrectionZ) { + std::vector& pz = isCrystal ? mCrystalZCorrectionPars : mSamplingZCorrectionPars; + float zCor = (pz[0] + pz[1] * eta + pz[2] * eta * eta) + (pz[3] + pz[4] * eta + pz[5] * eta * eta) * ee + (pz[6] + pz[7] * eta + pz[8] * eta * eta) * ee * ee; + cluster.setZ(z > 0 ? z - zCor : z + zCor); + } + + // check if cluster is at the edge of detector module + bool isEdge = 0; + for (size_t i = 0; i < cluster.getMultiplicity(); i++) { + int towerId = cluster.getDigitTowerId(i); + if (geo.isAtTheEdge(towerId)) { + isEdge = 1; + break; + } + } + cluster.setEdgeFlag(isEdge); + + LOGF(debug, "Cluster coordinates: (%6.2f,%6.2f,%6.2f)", cluster.getX(), cluster.getY(), cluster.getZ()); + } +} + +//============================================================================== +int Clusterizer::getNumberOfLocalMax(Cluster& clu, int* maxAt, float* maxAtEnergy) +{ + // Based on MpdEmcClusterizerKI::GetNumberOfLocalMax by D. Peresunko + // Calculates the number of local maxima in the cluster using LocalMaxCut as the minimum + // energy difference between maximum and surrounding digits + auto& geo = Geometry::instance(); + int n = clu.getMultiplicity(); + bool isCrystal = geo.isCrystal(clu.getDigitTowerId(0)); + bool* isLocalMax = new bool[n]; + + for (int i = 0; i < n; i++) { + isLocalMax[i] = false; + float en1 = clu.getDigitEnergy(i); + if (en1 > mClusteringThreshold) { + isLocalMax[i] = true; + } + } + + for (int i = 0; i < n; i++) { + int detId1 = clu.getDigitTowerId(i); + float en1 = clu.getDigitEnergy(i); + for (int j = i + 1; j < n; j++) { + int detId2 = clu.getDigitTowerId(j); + float en2 = clu.getDigitEnergy(j); + if (geo.areNeighboursVertex(detId1, detId2) == 1) { + if (en1 > en2) { + isLocalMax[j] = false; + // but may be digit too is not local max ? + if (en2 > en1 - mLocalMaximumCut) { + isLocalMax[i] = false; + } + } else { + isLocalMax[i] = false; + // but may be digitN is not local max too? + if (en1 > en2 - mLocalMaximumCut) { + isLocalMax[j] = false; + } + } + } // if neighbours + } // digit j + } // digit i + + int iDigitN = 0; + for (int i = 0; i < n; i++) { + if (isLocalMax[i]) { + maxAt[iDigitN] = i; + maxAtEnergy[iDigitN] = clu.getDigitEnergy(i); + iDigitN++; + if (iDigitN >= mNLMMax) { // Note that size of output arrays is limited: + LOGP(error, "Too many local maxima, cluster multiplicity {} region={}", n, isCrystal ? "crystal" : "sampling"); + return 0; + } + } + } + delete[] isLocalMax; + return iDigitN; +} + +//============================================================================== +double Clusterizer::showerShape(double dx, double dz, bool isCrystal) +{ + double x = std::sqrt(dx * dx + dz * dz); + return isCrystal ? fCrystalShowerShape->Eval(x) : fSamplingShowerShape->Eval(x); +} diff --git a/GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx b/Detectors/Upgrades/ALICE3/ECal/reconstruction/src/ECalReconstructionLinkDef.h similarity index 76% rename from GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx rename to Detectors/Upgrades/ALICE3/ECal/reconstruction/src/ECalReconstructionLinkDef.h index 1e2e540bdfdc8..d69cd8164e717 100644 --- a/GPU/TPCFastTransformation/NDPiecewisePolynomials.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/reconstruction/src/ECalReconstructionLinkDef.h @@ -9,7 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file NDPiecewisePolynomials.cxx -/// \author Matthias Kleiner +#ifdef __CLING__ -#include "NDPiecewisePolynomials.h" +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::ecal::Clusterizer + ; + +#endif diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/ECal/simulation/CMakeLists.txt index 8c8c5a6bba15f..83de48e38db3a 100644 --- a/Detectors/Upgrades/ALICE3/ECal/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/CMakeLists.txt @@ -11,8 +11,13 @@ o2_add_library(ECalSimulation SOURCES src/Detector.cxx + src/Digitizer.cxx PUBLIC_LINK_LIBRARIES O2::ECalBase - O2::ITSMFTSimulation) + O2::DataFormatsECal) o2_target_root_dictionary(ECalSimulation - HEADERS include/ECalSimulation/Detector.h) \ No newline at end of file + HEADERS include/ECalSimulation/Detector.h + include/ECalSimulation/Digitizer.h + ) + +o2_data_file(COPY data DESTINATION Detectors/ECL/simulation) \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/data/simcuts.dat b/Detectors/Upgrades/ALICE3/ECal/simulation/data/simcuts.dat new file mode 100644 index 0000000000000..81aa69990f222 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/data/simcuts.dat @@ -0,0 +1,14 @@ +* ECAL +* ==== +* +* Med GAM ELEC NHAD CHAD MUON EBREM MUHAB EDEL MUDEL MUPA ANNI BREM COMP DCAY DRAY HADR LOSS MULS PAIR PHOT RAYL STRA +* Air +ECL 0 5.e-5 1.e-4 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 -1. 1 1 1 1 1 1 1 1 1 1 1 -1 +* Lead +ECL 1 1.e-4 1.e-4 1.e-3 1.e-3 1.e-3 1.e-4 1.e-4 1.e-4 1.e-4 -1. 1 1 1 1 1 1 3 1 1 1 1 -1 +* Scintillator +ECL 2 1.e-4 1.e-4 1.e-3 1.e-3 1.e-3 1.e-4 1.e-4 1.e-4 1.e-4 -1. 1 1 1 1 1 1 3 1 1 1 1 -1 +* Aluminium +ECL 3 1.e-4 1.e-4 1.e-3 1.e-3 1.e-3 1.e-4 1.e-4 1.e-4 1.e-4 -1. 1 1 1 1 1 1 3 1 1 1 1 -1 +* PWO +ECL 4 5.e-5 1.e-4 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 1.e-5 -1. 1 1 1 1 1 1 1 1 1 1 1 -1 diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Detector.h b/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Detector.h index 14664092a8718..849dd69f85f2b 100644 --- a/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Detector.h @@ -8,18 +8,18 @@ // 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. -// -// Design and equations: Nicola Nicassio nicola.nicassio@cern.ch -// + +/// \file Detector.h +/// \brief ECal geometry creation and hit processing +/// +/// \author Evgeny Kryshen #ifndef ALICEO2_ECAL_DETECTOR_H #define ALICEO2_ECAL_DETECTOR_H -#include "DetectorsBase/Detector.h" -#include "ITSMFTSimulation/Hit.h" - -#include "ECalBase/GeometryTGeo.h" - +#include +#include +#include #include #include @@ -27,62 +27,35 @@ namespace o2 { namespace ecal { - class Detector : public o2::base::DetImpl { public: - Detector(bool active); - Detector(); + Detector(bool active = 1); ~Detector(); - void ConstructGeometry() override; - - o2::itsmft::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, - unsigned char startStatus, unsigned char endStatus); - // Mandatory overrides - void BeginPrimary() override { ; } - void FinishPrimary() override { ; } + void ConstructGeometry() override; + void BeginPrimary() override {} + void FinishPrimary() override {} void InitializeO2Detector() override; - void PostTrack() override { ; } - void PreTrack() override { ; } + void PostTrack() override {} + void PreTrack() override {} bool ProcessHits(FairVolume* v = nullptr) override; - void EndOfEvent() override; + void EndOfEvent() override { Reset(); } void Register() override; void Reset() override; + std::vector* getHits(int iColl) const { return !iColl ? mHits : nullptr; } - // Custom memer functions - std::vector* getHits(int iColl) const - { - if (!iColl) { - return mHits; - } - return nullptr; - } - + private: void createMaterials(); void createGeometry(); - - private: - // Transient data about track passing the sensor - struct TrackData { - bool mHitStarted; // hit creation started - unsigned char mTrkStatusStart; // track status flag - TLorentzVector mPositionStart; // position at entrance - TLorentzVector mMomentumStart; // momentum - double mEnergyLoss; // energy loss - } mTrackData; //! transient data - - GeometryTGeo* mGeometryTGeo; //! - std::vector* mHits; // ITSMFT ones for the moment - - void defineSensitiveVolumes(); - float mInnerRadius; - float mOuterRadius; - float mLength; - - bool mEnableEndcap{true}; + void defineSamplingFactor(); + std::unordered_map mSuperParentIndices; //! Super parent indices (track index - superparent index) + int currentTrackId = -1; // current track index + int superparentId = -1; // superparent index + GeometryTGeo* mGeometryTGeo; //! + std::vector* mHits; //! + double mSamplingFactorTransportModel = 1.; protected: template @@ -104,4 +77,5 @@ struct UseShm { } // namespace base } // namespace o2 #endif -#endif \ No newline at end of file + +#endif // ALICEO2_ECAL_DETECTOR_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Digitizer.h new file mode 100644 index 0000000000000..91213fa90b63a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/include/ECalSimulation/Digitizer.h @@ -0,0 +1,58 @@ +// 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 Digitization of ECal MC information +/// +/// \author Evgeny Kryshen + +#ifndef ALICEO2_ECAL_DIGITIZER_H +#define ALICEO2_ECAL_DIGITIZER_H +#include +#include +#include +#include +#include + +using o2::ecal::Digit; +using o2::ecal::Hit; +using o2::ecal::MCLabel; + +namespace o2 +{ +namespace ecal +{ +class Digitizer +{ + public: + Digitizer(); + ~Digitizer() = default; + Digitizer(const Digitizer&) = delete; + Digitizer& operator=(const Digitizer&) = delete; + void init() {} + void finish() {} + void processHits(const std::vector* mHits, std::vector& digits, o2::dataformats::MCTruthContainer& labels, int collId); + void setThreshold(double threshold) { mThreshold = threshold; } + void setSmearCrystal(bool smearCrystal) { mSmearCrystal = smearCrystal; } + void setSamplingFraction(double fraction) { mSamplingFraction = fraction; } + void setCrystalPePerGeV(double pePerGeV) { mCrystalPePerGeV = pePerGeV; } + + private: + std::vector mArrayD; + bool mSmearCrystal = 0; + double mThreshold = 0.001; + double mSamplingFraction = 9.8; + double mCrystalPePerGeV = 4000; +}; +} // namespace ecal +} // namespace o2 + +#endif // ALICEO2_ECAL_DIGITIZER_H diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/ECal/simulation/src/Detector.cxx index aeb58649fa4c5..f0de8aa4022a6 100644 --- a/Detectors/Upgrades/ALICE3/ECal/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/src/Detector.cxx @@ -9,45 +9,40 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include +/// \file Detector.cxx +/// \brief ECal geometry creation and hit processing +/// +/// \author Evgeny Kryshen +#include #include #include +#include #include #include -#include - -#include "DetectorsBase/Stack.h" -#include "ITSMFTSimulation/Hit.h" -#include "ECalSimulation/Detector.h" -#include "ECalBase/ECalBaseParam.h" - -using o2::itsmft::Hit; +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using o2::ecal::Hit; namespace o2 { namespace ecal { - -Detector::Detector() - : o2::base::DetImpl("ECL", true), - mTrackData(), - mHits(o2::utils::createSimVector()) -{ -} - Detector::Detector(bool active) - : o2::base::DetImpl("ECL", true), - mTrackData(), - mHits(o2::utils::createSimVector()) + : o2::base::DetImpl("ECL", active), + mHits(o2::utils::createSimVector()) { - auto& ecalPars = ECalBaseParam::Instance(); - mInnerRadius = ecalPars.rMin; - mOuterRadius = ecalPars.rMax; - mLength = ecalPars.zLength; - mEnableEndcap = ecalPars.enableFwdEndcap; } +//============================================================================== Detector::~Detector() { if (mHits) { @@ -55,190 +50,344 @@ Detector::~Detector() } } +//============================================================================== void Detector::ConstructGeometry() { createMaterials(); createGeometry(); + defineSamplingFactor(); } +//============================================================================== +void Detector::defineSamplingFactor() +{ + TString mcname = TVirtualMC::GetMC()->GetName(); + TString mctitle = TVirtualMC::GetMC()->GetTitle(); + LOGP(info, "Defining sampling factor for mc={}' and title='{}'", mcname.Data(), mctitle.Data()); + if (mcname.Contains("Geant3")) { + mSamplingFactorTransportModel = 0.983; + } else if (mcname.Contains("Geant4")) { + mSamplingFactorTransportModel = 1.; + } +} + +//============================================================================== void Detector::createMaterials() { - int ifield = 2; // ? - float fieldm = 10.0; // ? + LOGP(info, "Creating materials for ECL"); + + // Air + float aAir[4] = {12.0107, 14.0067, 15.9994, 39.948}; + float zAir[4] = {6., 7., 8., 18.}; + float wAir[4] = {0.000124, 0.755267, 0.231781, 0.012827}; + float dAir = 1.20479E-3; + Mixture(0, "Air", aAir, zAir, dAir, 4, wAir); + + // Pb + Material(1, "Pb", 207.2, 82, 11.35, 0.56, 0., nullptr, 0); + + // Polysterene scintillator (CH) + float aP[2] = {12.011, 1.00794}; + float zP[2] = {6.0, 1.0}; + float wP[2] = {1.0, 1.0}; + float dP = 1.032; + Mixture(2, "Scintillator", aP, zP, dP, -2, wP); + + // Al + Material(3, "Al", 26.98, 13., 2.7, 8.9, 999., nullptr, 0); + + // PWO crystals + float aX[3] = {207.19, 183.85, 16.0}; + float zX[3] = {82.0, 74.0, 8.0}; + float wX[3] = {1.0, 1.0, 4.0}; + float dX = 8.28; + Mixture(4, "PbWO4", aX, zX, dX, -3, wX); + + int ifield = 2; // magnetic field flag + float fieldm = 10.; // maximum field value (in Kilogauss) + float deemax = 0.1; // maximum fractional energy loss in one step (0 < deemax <=1) + float tmaxfd = 10.; // maximum angle due to field permitted in one step (in degrees) o2::base::Detector::initFieldTrackingParams(ifield, fieldm); - - float tmaxfdLead = 0.1; // .10000E+01; // Degree - float stemaxLead = .10000E+01; // cm - float deemaxLead = 0.1; // 0.30000E-02; // Fraction of particle's energy 0clear(); + } + mSuperParentIndices.clear(); + currentTrackId = -1; + superparentId = -1; } -void Detector::EndOfEvent() { Reset(); } - +//============================================================================== void Detector::Register() { // This will create a branch in the output tree called Hit, setting the last // parameter to kFALSE means that this collection will not be written to the file, // it will exist only during the simulation - + LOGP(info, "Registering hits"); if (FairRootManager::Instance()) { FairRootManager::Instance()->RegisterAny(addNameTo("Hit").data(), mHits, true); } } +//============================================================================== void Detector::createGeometry() { LOGP(info, "Creating ECal geometry"); - TGeoManager* geoManager = gGeoManager; - TGeoVolume* vALIC = geoManager->GetVolume("barrel"); + TGeoVolume* vALIC = gGeoManager->GetVolume("barrel"); if (!vALIC) { LOGP(fatal, "Could not find barrel volume while constructing ECal geometry"); } new TGeoVolumeAssembly(GeometryTGeo::getECalVolPattern()); - TGeoVolume* vECal = geoManager->GetVolume(GeometryTGeo::getECalVolPattern()); + TGeoVolume* vECal = gGeoManager->GetVolume(GeometryTGeo::getECalVolPattern()); vALIC->AddNode(vECal, 2, new TGeoTranslation(0, 30., 0)); + vECal->SetTitle("ECalVol"); + + TGeoMedium* medAir = gGeoManager->GetMedium("ECL_Air"); + TGeoMedium* medPb = gGeoManager->GetMedium("ECL_Pb"); + TGeoMedium* medAl = gGeoManager->GetMedium("ECL_Al"); + TGeoMedium* medSc = gGeoManager->GetMedium("ECL_Scintillator"); + TGeoMedium* medPWO = gGeoManager->GetMedium("ECL_Crystal"); + + // Get relevant parameters + auto& pars = ECalBaseParam::Instance(); + auto& geo = Geometry::instance(); + + double rMin = pars.rMin; + double rMax = pars.rMax; + double layerThickness = pars.pbLayerThickness + pars.scLayerThickness; + double samplingModL = pars.frontPlateThickness + layerThickness * pars.nSamplingLayers - pars.pbLayerThickness; + double crystalAlpha = geo.getCrystalAlpha(); + double samplingAlpha = geo.getSamplingAlpha(); + double tanCrystalAlpha = std::tan(crystalAlpha); + double tanSamplingAlpha = std::tan(samplingAlpha); + + double sectorL = rMax - rMin; + double crystalThetaMin = geo.getFrontFaceCenterTheta(pars.nCrystalModulesZ - 1) - crystalAlpha; + double crystalHlMin = geo.getFrontFaceZatMinR(pars.nCrystalModulesZ - 1); + double crystalHlMax = crystalHlMin + sectorL / std::tan(crystalThetaMin); + double crystalHwMin = geo.getCrystalModW() / 2.; + double crystalHwMax = crystalHwMin * rMax / rMin; + auto crystalSectorShape = new TGeoTrap(sectorL / 2., 0, 0, crystalHlMin, crystalHwMin, crystalHwMin, 0, crystalHlMax, crystalHwMax, crystalHwMax, 0); + auto crystalSectorVolume = new TGeoVolume("crystalSectorVolume", crystalSectorShape, medAir); + AddSensitiveVolume(crystalSectorVolume); + crystalSectorVolume->SetLineColor(kCyan + 1); + crystalSectorVolume->SetTransparency(0); + + double samplingThetaAtMinZ = geo.getFrontFaceCenterTheta(pars.nCrystalModulesZ) + samplingAlpha; + double samplingThetaAtMaxZ = geo.getFrontFaceCenterTheta(pars.nCrystalModulesZ + pars.nSamplingModulesZ - 1) - samplingAlpha; + double samplingMinZatMinR = geo.getFrontFaceZatMinR(pars.nCrystalModulesZ) - geo.getSamplingModW() / std::sin(samplingThetaAtMinZ); + double samplingMaxZatMinR = geo.getFrontFaceZatMinR(pars.nCrystalModulesZ + pars.nSamplingModulesZ - 1); + double samplingMinZatMaxR = samplingMinZatMinR + sectorL / std::tan(samplingThetaAtMinZ); + double samplingMaxZatMaxR = samplingMaxZatMinR + sectorL / std::tan(samplingThetaAtMaxZ); + double hlMin = (samplingMaxZatMinR - samplingMinZatMinR) / 2.; + double hlMax = (samplingMaxZatMaxR - samplingMinZatMaxR) / 2.; + double zCenterMin = (samplingMaxZatMinR + samplingMinZatMinR) / 2.; + double zCenterMax = (samplingMaxZatMaxR + samplingMinZatMaxR) / 2.; + double zCenter = (zCenterMax + zCenterMin) / 2.; + double thetaCenter = std::atan((zCenterMax - zCenterMin) / sectorL) * TMath::RadToDeg(); + double samplingHwMin = geo.getSamplingModW() / 2.; + double samplingHwMax = samplingHwMin * rMax / rMin; + auto samplingSectorShape = new TGeoTrap(sectorL / 2., thetaCenter, 90, hlMin, samplingHwMin, samplingHwMin, 0, hlMax, samplingHwMax, samplingHwMax, 0); + auto samplingSectorVolume = new TGeoVolume("samplingSectorVolume", samplingSectorShape, medAir); + AddSensitiveVolume(samplingSectorVolume); + samplingSectorVolume->SetLineColor(kBlue + 1); + samplingSectorVolume->SetTransparency(0); + + double sectorR = rMin + sectorL / 2.; + for (int ism = 0; ism < pars.nSuperModules; ism++) { + // crystal + for (int i = 0; i < pars.nCrystalModulesPhi; i++) { + int row = ism * pars.nCrystalModulesPhi + i; + double phi0 = geo.getFrontFaceCenterCrystalPhi(row); + double x = sectorR * std::cos(phi0); + double y = sectorR * std::sin(phi0); + auto rot = new TGeoRotation(Form("ecalcrystalsecrot%d", row), 90 + phi0 * TMath::RadToDeg(), 90, 0); + vECal->AddNode(crystalSectorVolume, row, new TGeoCombiTrans(x, y, 0., rot)); + } + // sampling + for (int i = 0; i < pars.nSamplingModulesPhi; i++) { + int row = ism * pars.nSamplingModulesPhi + i; + double phi0 = geo.getFrontFaceCenterSamplingPhi(row); + double x = sectorR * std::cos(phi0); + double y = sectorR * std::sin(phi0); + auto rot1 = new TGeoRotation(Form("ecalsamplingsec1rot%d", row), 90 + phi0 * TMath::RadToDeg(), 90, 0.); + auto rot2 = new TGeoRotation(Form("ecalsamplingsec2rot%d", row), 90 + phi0 * TMath::RadToDeg(), 90, 180); + vECal->AddNode(samplingSectorVolume, 2 * row + 0, new TGeoCombiTrans(x, y, zCenter, rot1)); + vECal->AddNode(samplingSectorVolume, 2 * row + 1, new TGeoCombiTrans(x, y, -zCenter, rot2)); + } + } - char vstrng[100] = "ECalVol"; - vECal->SetTitle(vstrng); + for (int m = 0; m < pars.nCrystalModulesZ; m++) { + double tanBeta = geo.getTanBeta(m); + double dx1 = crystalHwMin; + double dx2 = crystalHwMin + pars.crystalModuleLength * tanCrystalAlpha; + double dy1 = crystalHwMin; + double dy2 = crystalHwMin + pars.crystalModuleLength * tanBeta; + double dz = pars.crystalModuleLength / 2.; + auto crystalModuleShape = new TGeoTrd2(dx1, dx2, dy1, dy2, dz); + auto crystalModuleVolume = new TGeoVolume(Form("crystalmodule%d", m), crystalModuleShape, medPWO); + AddSensitiveVolume(crystalModuleVolume); + crystalModuleVolume->SetLineColor(kCyan + 1); + crystalModuleVolume->SetTransparency(0); + double theta = geo.getFrontFaceCenterTheta(m); + double r = geo.getFrontFaceCenterR(m); + double z = geo.getFrontFaceCenterZ(m); + ROOT::Math::XYPoint pFrontFace(z, r - sectorR); + ROOT::Math::Polar2DVector vFrontFaceToCenter(dz, theta); + ROOT::Math::XYPoint pc = pFrontFace + vFrontFaceToCenter; + auto rot1 = new TGeoRotation(Form("ecalcrystalrot%d", 2 * m), 0, 270 + theta * TMath::RadToDeg(), 90); + crystalSectorVolume->AddNode(crystalModuleVolume, 2 * m, new TGeoCombiTrans(0, pc.x(), pc.y(), rot1)); + auto rot2 = new TGeoRotation(Form("ecalcrystalrot%d", 2 * m + 1), 0, 90 - theta * TMath::RadToDeg(), 90); + crystalSectorVolume->AddNode(crystalModuleVolume, 2 * m + 1, new TGeoCombiTrans(0, -pc.x(), pc.y(), rot2)); + } - // Build the ECal cylinder - auto& matmgr = o2::base::MaterialManager::Instance(); - TGeoMedium* medPb = matmgr.getTGeoMedium("ECL_LEAD"); - TGeoTube* ecalShape = new TGeoTube("ECLsh", mInnerRadius, mOuterRadius, mLength); - TGeoVolume* ecalVol = new TGeoVolume("ECL", ecalShape, medPb); - ecalVol->SetLineColor(kAzure - 9); - ecalVol->SetTransparency(0); - vECal->AddNode(ecalVol, 1, nullptr); + for (int m = 0; m < pars.nSamplingModulesZ; m++) { + int k = pars.nCrystalModulesZ + m; + double tanBeta = geo.getTanBeta(k); + double dx1 = samplingHwMin; + double dx2 = samplingHwMin + samplingModL * tanSamplingAlpha; + double dy1 = samplingHwMin; + double dy2 = samplingHwMin + samplingModL * tanBeta; + double dz = samplingModL / 2.; + auto samplingModuleShape = new TGeoTrd2(dx1, dx2, dy1, dy2, dz); + auto samplingModuleVolume = new TGeoVolume(Form("samplingmodule%d", m), samplingModuleShape, medSc); + AddSensitiveVolume(samplingModuleVolume); + samplingModuleVolume->SetLineColor(kAzure - 9); + samplingModuleVolume->SetTransparency(0); + double theta = geo.getFrontFaceCenterTheta(k); + double r = geo.getFrontFaceCenterR(k); + double z = geo.getFrontFaceCenterZ(k); + ROOT::Math::XYPoint pFrontFace(z - zCenter, r - sectorR); + ROOT::Math::Polar2DVector vFrontFaceToCenter(dz, theta); + ROOT::Math::XYPoint pc = pFrontFace + vFrontFaceToCenter; + auto rot1 = new TGeoRotation(Form("ecalsamplingrot%d", m), 0, 270 + theta * TMath::RadToDeg(), 90); + samplingSectorVolume->AddNode(samplingModuleVolume, m, new TGeoCombiTrans(0, pc.x(), pc.y(), rot1)); + + // adding front aluminium plate into the volume + double fdx1 = dx1; + double fdx2 = dx1 + pars.frontPlateThickness * tanSamplingAlpha; + double fdy1 = dy1; + double fdy2 = fdy1 + pars.frontPlateThickness * tanBeta; + double fdz = pars.frontPlateThickness / 2.; + auto frontShape = new TGeoTrd2(fdx1, fdx2, fdy1, fdy2, fdz); + auto frontVolume = new TGeoVolume(Form("front%d", m), frontShape, medAl); + samplingModuleVolume->AddNode(frontVolume, 0, new TGeoTranslation(0., 0., -dz + pars.frontPlateThickness / 2.)); + AddSensitiveVolume(frontVolume); + frontVolume->SetLineColor(kAzure - 7); + frontVolume->SetTransparency(0); + + // adding lead plates + for (int i = 0; i < pars.nSamplingLayers - 1; i++) { + double lz1 = pars.frontPlateThickness + pars.scLayerThickness + layerThickness * i; + double lz2 = lz1 + pars.pbLayerThickness; + double lzc = -dz + (lz1 + lz2) / 2.; + double ldx1 = dx1 + lz1 * tanSamplingAlpha; + double ldx2 = dx1 + lz2 * tanSamplingAlpha; + double ldy1 = dy1 + lz1 * tanBeta; + double ldy2 = dy1 + lz2 * tanBeta; + double ldz = pars.pbLayerThickness / 2.; + auto leadShape = new TGeoTrd2(ldx1, ldx2, ldy1, ldy2, ldz); + auto leadVolume = new TGeoVolume(Form("lead%d_%d", m, i), leadShape, medPb); + samplingModuleVolume->AddNode(leadVolume, i, new TGeoTranslation(0., 0., lzc)); + AddSensitiveVolume(leadVolume); + leadVolume->SetLineColor(kAzure - 7); + leadVolume->SetTransparency(0); + } + } - if (mEnableEndcap) { + if (pars.enableFwdEndcap) { // Build the ecal endcap - TGeoTube* ecalEndcapShape = new TGeoTube("ECLECsh", 15.f, 160.f, 0.5 * (mOuterRadius - mInnerRadius)); + TGeoTube* ecalEndcapShape = new TGeoTube("ECLECsh", 15.f, 160.f, 0.5 * (rMax - rMin)); TGeoVolume* ecalEndcapVol = new TGeoVolume("ECLEC", ecalEndcapShape, medPb); ecalEndcapVol->SetLineColor(kAzure - 9); ecalEndcapVol->SetTransparency(0); vECal->AddNode(ecalEndcapVol, 1, new TGeoTranslation(0, 0, -450.f)); } + // gGeoManager->CloseGeometry(); + // gGeoManager->CheckOverlaps(0.0001); } -void Detector::Reset() -{ - if (!o2::utils::ShmManager::Instance().isOperational()) { - mHits->clear(); - } -} - +//============================================================================== bool Detector::ProcessHits(FairVolume* vol) { - // This method is called from the MC stepping - if (!(fMC->TrackCharge())) { - return false; - } - - int lay = vol->getVolumeId(); - int volID = vol->getMCid(); - - // Is it needed to keep a track reference when the outer ITS volume is encountered? + LOGP(debug, "Processing hits"); auto stack = (o2::data::Stack*)fMC->GetStack(); - if (fMC->IsTrackExiting() && (lay == 0)) { - o2::TrackReference tr(*fMC, GetDetId()); - tr.setTrackID(stack->GetCurrentTrackNumber()); - tr.setUserId(lay); - stack->addTrackReference(tr); - } - bool startHit = false, stopHit = false; - unsigned char status = 0; - if (fMC->IsTrackEntering()) { - status |= Hit::kTrackEntering; - } - if (fMC->IsTrackInside()) { - status |= Hit::kTrackInside; - } - if (fMC->IsTrackExiting()) { - status |= Hit::kTrackExiting; - } - if (fMC->IsTrackOut()) { - status |= Hit::kTrackOut; - } - if (fMC->IsTrackStop()) { - status |= Hit::kTrackStopped; - } - if (fMC->IsTrackAlive()) { - status |= Hit::kTrackAlive; + int trackId = stack->GetCurrentTrackNumber(); + int parentId = stack->GetCurrentParentTrackNumber(); + + if (trackId != currentTrackId) { + auto superparentIndexIt = mSuperParentIndices.find(parentId); + if (superparentIndexIt != mSuperParentIndices.end()) { + superparentId = superparentIndexIt->second; + mSuperParentIndices[trackId] = superparentIndexIt->second; + } else { + // for new incoming tracks the superparent index is equal to the track ID (for recursion) + mSuperParentIndices[trackId] = trackId; + superparentId = trackId; + } + currentTrackId = trackId; } - // track is entering or created in the volume - if ((status & Hit::kTrackEntering) || (status & Hit::kTrackInside && !mTrackData.mHitStarted)) { - startHit = true; - } else if ((status & (Hit::kTrackExiting | Hit::kTrackOut | Hit::kTrackStopped))) { - stopHit = true; + double eloss = fMC->Edep(); + if (eloss < DBL_EPSILON) { + return false; } - // increment energy loss at all steps except entrance - if (!startHit) { - mTrackData.mEnergyLoss += fMC->Edep(); - } - if (!(startHit | stopHit)) { - return false; // do noting + TString volName = vol->GetName(); + bool isCrystal = volName.Contains("crystalmodule"); + bool isSampling = volName.Contains("samplingmodule"); + + if (!isCrystal && !isSampling) { + return false; } - if (startHit) { - mTrackData.mEnergyLoss = 0.; - fMC->TrackMomentum(mTrackData.mMomentumStart); - fMC->TrackPosition(mTrackData.mPositionStart); - mTrackData.mTrkStatusStart = status; - mTrackData.mHitStarted = true; + if (isCrystal) { + LOGP(debug, "Processing crystal {}", volName.Data()); + } else { + eloss *= mSamplingFactorTransportModel; + LOGP(debug, "Processing scintillator {}", volName.Data()); } - if (stopHit) { - TLorentzVector positionStop; - fMC->TrackPosition(positionStop); - // Retrieve the indices with the volume path - int stave(0), halfstave(0), chipinmodule(0), module; - fMC->CurrentVolOffID(1, chipinmodule); - fMC->CurrentVolOffID(2, module); - fMC->CurrentVolOffID(3, halfstave); - fMC->CurrentVolOffID(4, stave); - - Hit* p = addHit(stack->GetCurrentTrackNumber(), lay, mTrackData.mPositionStart.Vect(), positionStop.Vect(), - mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), - mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status); - // p->SetTotalEnergy(vmc->Etot()); - - // RS: not sure this is needed - // Increment number of Detector det points in TParticle + int sectorId, moduleId; + fMC->CurrentVolID(moduleId); + fMC->CurrentVolOffID(1, sectorId); + int cellID = Geometry::instance().getCellID(moduleId, sectorId, isCrystal); + LOGP(debug, "isCrystal={} sectorId={} moduleId={} cellID={} eloss={}", isCrystal, sectorId, moduleId, cellID, eloss); + + int trackID = superparentId; + auto hit = std::find_if(mHits->begin(), mHits->end(), [cellID, trackID](const Hit& hit) { return hit.GetTrackID() == trackID && hit.GetCellID() == cellID; }); + if (hit == mHits->end()) { + float posX, posY, posZ, momX, momY, momZ, energy; + fMC->TrackPosition(posX, posY, posZ); + fMC->TrackMomentum(momX, momY, momZ, energy); + auto pos = math_utils::Point3D(posX, posY, posZ); + auto mom = math_utils::Vector3D(momX, momY, momZ); + float time = fMC->TrackTime() * 1e9; // time in ns + mHits->emplace_back(trackID, cellID, pos, mom, time, eloss); stack->addHit(GetDetId()); + } else { + hit->SetEnergyLoss(hit->GetEnergyLoss() + eloss); } - return true; } -o2::itsmft::Hit* Detector::addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, - unsigned char endStatus) -{ - mHits->emplace_back(trackID, detID, startPos, endPos, startMom, startE, endTime, eLoss, startStatus, endStatus); - return &(mHits->back()); -} - } // namespace ecal } // namespace o2 -ClassImp(o2::ecal::Detector); \ No newline at end of file +ClassImp(o2::ecal::Detector); diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ALICE3/ECal/simulation/src/Digitizer.cxx new file mode 100644 index 0000000000000..42c1908a29d18 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/src/Digitizer.cxx @@ -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 Digitizer.cxx +/// \brief Digitization of ECal MC information +/// +/// \author Evgeny Kryshen + +#include +#include +#include + +#include +#include +#include +#include + +using namespace o2::ecal; + +//============================================================================== +Digitizer::Digitizer() +{ + auto& geo = Geometry::instance(); + mArrayD.resize(geo.getNrows() * geo.getNcols()); +} + +//============================================================================== +void Digitizer::processHits(const std::vector* hits, std::vector& digits, o2::dataformats::MCTruthContainer& labels, int collId) +{ + digits.clear(); + labels.clear(); + + LOGP(debug, "nHits = {}", hits->size()); + auto& geo = Geometry::instance(); + + for (int i = 0; i < mArrayD.size(); i++) { + mArrayD[i].setAmplitude(0); + mArrayD[i].setTimeStamp(1000); + mArrayD[i].setTower(i); + mArrayD[i].setLabel(-1); + // TODO: simulate noise + } + + for (auto& hit : *hits) { + int cellID = hit.GetCellID(); + double eloss = hit.GetEnergyLoss(); + double t = hit.GetTime(); + double elossSmeared = eloss; + bool isCrystal = geo.isCrystal(cellID); + if (isCrystal) { // crystal + double elossSmearedNpe = gRandom->Poisson(eloss * mCrystalPePerGeV) / mCrystalPePerGeV; + if (mSmearCrystal) { + elossSmeared = elossSmearedNpe * gRandom->Gaus(1, 0.007); // light attenuation in crystals + } + } else { // sampling + elossSmeared *= mSamplingFraction; + } + + Digit& digit = mArrayD[cellID]; + digit.setAmplitude(digit.getAmplitude() + elossSmeared); + if (t < digit.getTimeStamp()) { + digit.setTimeStamp(t); // setting earliest time, TODO: add time smearing + } + LOGF(debug, " crystal: %d cellID = %5d, eloss = %8.5f elossSmeared = %8.5f time = %8.5f", isCrystal, cellID, eloss, elossSmeared, t); + + // Adding MC info + MCLabel label(hit.GetTrackID(), collId, 0, false, hit.GetEnergyLoss()); + int labelIndex = digit.getLabel(); + if (labelIndex == -1) { + labelIndex = labels.getIndexedSize(); + labels.addElement(labelIndex, label); + digit.setLabel(labelIndex); + } else { + labels.addElementRandomAccess(labelIndex, label); + } + } // hits + + for (int i = 0; i < mArrayD.size(); i++) { + if (mArrayD[i].getAmplitude() > mThreshold) { + digits.push_back(mArrayD[i]); + } + } +} diff --git a/Detectors/Upgrades/ALICE3/ECal/simulation/src/ECalSimulationLinkDef.h b/Detectors/Upgrades/ALICE3/ECal/simulation/src/ECalSimulationLinkDef.h index 167342773f196..5d7383f086362 100644 --- a/Detectors/Upgrades/ALICE3/ECal/simulation/src/ECalSimulationLinkDef.h +++ b/Detectors/Upgrades/ALICE3/ECal/simulation/src/ECalSimulationLinkDef.h @@ -17,5 +17,6 @@ #pragma link C++ class o2::ecal::Detector + ; #pragma link C++ class o2::base::DetImpl < o2::ecal::Detector> + ; +#pragma link C++ class o2::ecal::Digitizer + ; #endif diff --git a/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt b/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt new file mode 100644 index 0000000000000..d9ea4b632952c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright 2019-2025 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(base) +add_subdirectory(simulation) diff --git a/Detectors/Upgrades/ALICE3/FD3/README.md b/Detectors/Upgrades/ALICE3/FD3/README.md new file mode 100644 index 0000000000000..54c1fd37b2590 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/README.md @@ -0,0 +1,10 @@ + + +# ALICE 3 FORWARD DETECTOR + +This is top page for the FD3 detector documentation. + + diff --git a/Detectors/Upgrades/ALICE3/FD3/base/CMakeLists.txt b/Detectors/Upgrades/ALICE3/FD3/base/CMakeLists.txt new file mode 100644 index 0000000000000..c76665da1344f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright 2019-2025 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(FD3Base + SOURCES src/GeometryTGeo.cxx + src/FD3BaseParam.cxx + PUBLIC_LINK_LIBRARIES O2::DetectorsBase) + +o2_target_root_dictionary(FD3Base + HEADERS include/FD3Base/GeometryTGeo.h + include/FD3Base/Constants.h + include/FD3Base/FD3BaseParam.h) diff --git a/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/Constants.h b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/Constants.h new file mode 100644 index 0000000000000..428a7a1f6d179 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/Constants.h @@ -0,0 +1,38 @@ +// 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 Constants.h +/// \brief General constants in FV0 +/// +/// \author Maciej Slupecki, University of Jyvaskyla, Finland + +#ifndef ALICEO2_FD3_CONSTANTS_ +#define ALICEO2_FD3_CONSTANTS_ + +namespace o2 +{ +namespace fd3 +{ +struct Constants { + static constexpr unsigned int nsect = 8; + static constexpr unsigned int nringsA = 5; + static constexpr unsigned int nringsC = 6; + + static constexpr float etaMax = 7.0f; + static constexpr float etaMin = 4.0f; + + static constexpr unsigned int nringsA_withMG = 3; + static constexpr float etaMinA_withMG = 5.0f; +}; + +} // namespace fd3 +} // namespace o2 +#endif diff --git a/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/FD3BaseParam.h b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/FD3BaseParam.h new file mode 100644 index 0000000000000..9836cebbfa760 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/FD3BaseParam.h @@ -0,0 +1,41 @@ +// 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_FD3_FD3BASEPARAM_ +#define ALICEO2_FD3_FD3BASEPARAM_ + +#include "FD3Base/GeometryTGeo.h" +#include "FD3Base/Constants.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +namespace o2 +{ +namespace fd3 +{ +struct FD3BaseParam : public o2::conf::ConfigurableParamHelper { + + float zmodA = 1700.0f; + float zmodC = -1850.0f; + float dzscint = 4.0f; + + bool withMG = false; // modified geometry with 3 rings on A side + + bool plateBehindA = false; + bool fullContainer = false; + float dzplate = 1.0f; // Aluminium plate width + + O2ParamDef(FD3BaseParam, "FD3Base"); +}; + +} // namespace fd3 +} // namespace o2 + +#endif diff --git a/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/GeometryTGeo.h new file mode 100644 index 0000000000000..0e38bd4ccd21f --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/include/FD3Base/GeometryTGeo.h @@ -0,0 +1,54 @@ +// 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_FD3_GEOMETRYTGEO_H_ +#define ALICEO2_FD3_GEOMETRYTGEO_H_ + +#include + +#include "DetectorsBase/GeometryManager.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include +#include +#include +#include +#include +#include +#include + +namespace o2 +{ +namespace fd3 +{ + +/// FD3 Geometry type +class GeometryTGeo : public o2::detectors::DetMatrixCache +{ + public: + GeometryTGeo(bool build = false, int loadTrans = 0); + + void Build(int loadTrans); + void fillMatrixCache(int mask); + virtual ~GeometryTGeo(); + + static GeometryTGeo* Instance(); + + void getGlobalPosition(float& x, float& y, float& z); + + static constexpr o2::detectors::DetID::ID getDetID() { return o2::detectors::DetID::FD3; } + + private: + static std::unique_ptr sInstance; + + ClassDefNV(GeometryTGeo, 1); +}; +} // namespace fd3 +} // namespace o2 +#endif diff --git a/Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseLinkDef.h b/Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseLinkDef.h new file mode 100644 index 0000000000000..8475ef2c77313 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseLinkDef.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. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::fd3::Constants + ; +#pragma link C++ class o2::fd3::GeometryTGeo + ; +#pragma link C++ class o2::fd3::FD3BaseParam + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::fd3::FD3BaseParam> + ; + +#endif diff --git a/Framework/TestWorkflows/src/dummy.cxx b/Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseParam.cxx similarity index 88% rename from Framework/TestWorkflows/src/dummy.cxx rename to Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseParam.cxx index faff430964e73..74b45962b3f39 100644 --- a/Framework/TestWorkflows/src/dummy.cxx +++ b/Detectors/Upgrades/ALICE3/FD3/base/src/FD3BaseParam.cxx @@ -8,3 +8,7 @@ // 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 "FD3Base/FD3BaseParam.h" + +O2ParamImpl(o2::fd3::FD3BaseParam); diff --git a/Detectors/Upgrades/ALICE3/FD3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/FD3/base/src/GeometryTGeo.cxx new file mode 100644 index 0000000000000..16788cb8944e3 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/base/src/GeometryTGeo.cxx @@ -0,0 +1,66 @@ +// 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 "FD3Base/GeometryTGeo.h" +#include "FD3Base/FD3BaseParam.h" + +#include + +#include + +using namespace o2::fd3; +namespace o2 +{ +namespace fd3 +{ + +std::unique_ptr GeometryTGeo::sInstance; + +GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : DetMatrixCache() +{ + if (sInstance) { + LOGP(fatal, "Invalid use of public constructor: o2::fd3::GeometryTGeo instance exists"); + } + if (build) { + Build(loadTrans); + } +} + +GeometryTGeo::~GeometryTGeo() = default; + +GeometryTGeo* GeometryTGeo::Instance() +{ + if (!sInstance) { + sInstance = std::unique_ptr(new GeometryTGeo(true, 0)); + } + return sInstance.get(); +} + +void GeometryTGeo::Build(int loadTrans) +{ + if (isBuilt()) { + LOGP(warning, "Already built"); + return; // already initialized + } + + if (!gGeoManager) { + LOGP(fatal, "Geometry is not loaded"); + } + + fillMatrixCache(loadTrans); +} + +void GeometryTGeo::fillMatrixCache(int mask) +{ +} + +} // namespace fd3 +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/FD3/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/FD3/simulation/CMakeLists.txt new file mode 100644 index 0000000000000..38886ec5fbe07 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/simulation/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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(FD3Simulation + SOURCES src/Detector.cxx + PUBLIC_LINK_LIBRARIES O2::FD3Base + O2::DataFormatsFD3 + ROOT::Physics) + +o2_target_root_dictionary(FD3Simulation + HEADERS include/FD3Simulation/Detector.h) diff --git a/Detectors/Upgrades/ALICE3/FD3/simulation/include/FD3Simulation/Detector.h b/Detectors/Upgrades/ALICE3/FD3/simulation/include/FD3Simulation/Detector.h new file mode 100644 index 0000000000000..2d17acbd4a0e8 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/simulation/include/FD3Simulation/Detector.h @@ -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. + +/// \file Detector.h +/// \brief Definition of the Detector class + +#ifndef ALICEO2_FD3_DETECTOR_H_ +#define ALICEO2_FD3_DETECTOR_H_ + +#include "SimulationDataFormat/BaseHits.h" +#include "DetectorsBase/Detector.h" +#include "FD3Base/GeometryTGeo.h" +#include "FD3Base/FD3BaseParam.h" +#include "DataFormatsFD3/Hit.h" +#include "Rtypes.h" +#include "TGeoManager.h" +#include "TLorentzVector.h" +#include "TVector3.h" +#include + +class FairVolume; +class TGeoVolume; + +namespace o2 +{ +namespace fd3 +{ +class GeometryTGeo; +} +} // namespace o2 + +namespace o2 +{ +namespace fd3 +{ + +class Detector : public o2::base::DetImpl +{ + public: + Detector(bool Active); + + Detector() = default; + + ~Detector() override; + + void ConstructGeometry() override; + + /// This method is an example of how to add your own point of type Hit to the clones array + o2::fd3::Hit* addHit(int trackId, unsigned int detId, + const math_utils::Point3D& startPos, + const math_utils::Point3D& endPos, + const math_utils::Vector3D& startMom, double startE, + double endTime, double eLoss, int particlePdg); + // unsigned int startStatus, + // unsigned int endStatus); + + std::vector* getHits(Int_t iColl) + { + if (iColl == 0) { + return mHits; + } + return nullptr; + } + + // Mandatory overrides + void BeginPrimary() override { ; } + void FinishPrimary() override { ; } + void InitializeO2Detector() override; + void PostTrack() override { ; } + void PreTrack() override { ; } + bool ProcessHits(FairVolume* v = nullptr) override; + void EndOfEvent() override; + void Register() override; + void Reset() override; + + void createMaterials(); + void buildModules(); + + enum EMedia { + Scintillator, + Aluminium + }; + + private: + Detector(const Detector&); + Detector& operator=(const Detector&); + + std::vector* mHits = nullptr; + GeometryTGeo* mGeometryTGeo = nullptr; + + TGeoVolumeAssembly* buildModuleA(); + TGeoVolumeAssembly* buildModuleC(); + + float ringSize(float zmod, float eta); + + unsigned int mNumberOfRingsA, mNumberOfRingsC, mNumberOfSectors; + float mDzScint, mDzPlate; + + std::vector mRingSizesA = {}, mRingSizesC = {}; + + float mEtaMaxA, mEtaMaxC, mEtaMinA, mEtaMinC; + float mZA, mZC; + + bool mPlateBehindA, mFullContainer; + + void defineSensitiveVolumes(); + void definePassiveVolumes(); + + /// Transient data about track passing the sensor, needed by ProcessHits() + struct TrackData { // this is transient + bool mHitStarted; //! hit creation started + unsigned char mTrkStatusStart; //! track status flag + TLorentzVector mPositionStart; //! position at entrance + TLorentzVector mMomentumStart; //! momentum + double mEnergyLoss; //! energy loss + } mTrackData; //! + + template + friend class o2::base::DetImpl; + ClassDefOverride(Detector, 1); +}; + +// Input and output function for standard C++ input/output. +std::ostream& operator<<(std::ostream& os, Detector& source); +std::istream& operator>>(std::istream& os, Detector& source); + +} // namespace fd3 +} // namespace o2 + +#ifdef USESHM +namespace o2 +{ +namespace base +{ +template <> +struct UseShm { + static constexpr bool value = true; +}; +} // namespace base +} // namespace o2 +#endif +#endif diff --git a/Detectors/Upgrades/ALICE3/FD3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/FD3/simulation/src/Detector.cxx new file mode 100644 index 0000000000000..bd79b1deaad80 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FD3/simulation/src/Detector.cxx @@ -0,0 +1,413 @@ +// 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 Detector.cxx +/// \brief Implementation of the Detector class + +#include "DataFormatsFD3/Hit.h" +#include "FD3Simulation/Detector.h" +#include "FD3Base/GeometryTGeo.h" +#include "FD3Base/FD3BaseParam.h" +#include "FD3Base/Constants.h" + +#include "DetectorsBase/Stack.h" +#include "SimulationDataFormat/TrackReference.h" +#include "Field/MagneticField.h" + +// FairRoot includes +#include "FairDetector.h" +#include +#include "FairRootManager.h" +#include "FairRun.h" +#include "FairRuntimeDb.h" +#include "FairVolume.h" +#include "FairRootManager.h" + +#include "TVirtualMC.h" +#include "TLorentzVector.h" +#include "TVector3.h" +#include +#include +#include +#include +#include +#include +#include "TRandom.h" +#include + +class FairModule; + +class TGeoMedium; + +using namespace o2::fd3; +using o2::fd3::Hit; + +Detector::Detector(bool active) + : o2::base::DetImpl("FD3", true), + mHits(o2::utils::createSimVector()), + mGeometryTGeo(nullptr), + mTrackData() +{ + mNumberOfRingsC = Constants::nringsC; + mNumberOfSectors = Constants::nsect; + + mEtaMinA = Constants::etaMin; + mEtaMaxA = Constants::etaMax; + mEtaMinC = -Constants::etaMax; + mEtaMaxC = -Constants::etaMin; + + auto& baseParam = FD3BaseParam::Instance(); + + if (baseParam.withMG) { + mNumberOfRingsA = Constants::nringsA_withMG; + mEtaMinA = Constants::etaMinA_withMG; + } else { + mNumberOfRingsA = Constants::nringsA; + mEtaMinA = Constants::etaMin; + } + + mDzScint = baseParam.dzscint / 2; + mDzPlate = baseParam.dzplate; + + mPlateBehindA = baseParam.plateBehindA; + mFullContainer = baseParam.fullContainer; + + mZA = baseParam.zmodA; + mZC = baseParam.zmodC; + + for (int i = 0; i <= mNumberOfRingsA + 1; i++) { + float eta = mEtaMaxA - i * (mEtaMaxA - mEtaMinA) / mNumberOfRingsA; + float r = ringSize(mZA, eta); + mRingSizesA.emplace_back(r); + } + + for (int i = 0; i <= mNumberOfRingsC + 1; i++) { + float eta = mEtaMinC + i * (mEtaMaxC - mEtaMinC) / mNumberOfRingsC; + float r = ringSize(mZC, eta); + mRingSizesC.emplace_back(r); + } +} + +Detector::Detector(const Detector& rhs) + : o2::base::DetImpl(rhs), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + +Detector& Detector::operator=(const Detector& rhs) +{ + + if (this == &rhs) { + return *this; + } + // base class assignment + base::Detector::operator=(rhs); + mTrackData = rhs.mTrackData; + + mHits = nullptr; + return *this; +} + +Detector::~Detector() +{ + + if (mHits) { + o2::utils::freeSimVector(mHits); + } +} + +void Detector::InitializeO2Detector() +{ + LOG(info) << "Initialize Forward Detector"; + mGeometryTGeo = GeometryTGeo::Instance(); + defineSensitiveVolumes(); +} + +bool Detector::ProcessHits(FairVolume* vol) +{ + // This method is called from the MC stepping + if (!(fMC->TrackCharge())) { + return kFALSE; + } + + int detId; + int volID = fMC->CurrentVolID(detId); + + auto stack = (o2::data::Stack*)fMC->GetStack(); + + // Check track status to define when hit is started and when it is stopped + int particlePdg = fMC->TrackPid(); + bool startHit = false, stopHit = false; + if ((fMC->IsTrackEntering()) || (fMC->IsTrackInside() && !mTrackData.mHitStarted)) { + startHit = true; + } else if ((fMC->IsTrackExiting() || fMC->IsTrackOut() || fMC->IsTrackStop())) { + stopHit = true; + } + + // increment energy loss at all steps except entrance + if (!startHit) { + mTrackData.mEnergyLoss += fMC->Edep(); + } + if (!(startHit | stopHit)) { + return kFALSE; // do noting + } + + if (startHit) { + mTrackData.mHitStarted = true; + mTrackData.mEnergyLoss = 0.; + fMC->TrackMomentum(mTrackData.mMomentumStart); + fMC->TrackPosition(mTrackData.mPositionStart); + mTrackData.mTrkStatusStart = true; + } + + if (stopHit) { + TLorentzVector positionStop; + fMC->TrackPosition(positionStop); + int trackId = stack->GetCurrentTrackNumber(); + + math_utils::Point3D posStart(mTrackData.mPositionStart.X(), mTrackData.mPositionStart.Y(), mTrackData.mPositionStart.Z()); + math_utils::Point3D posStop(positionStop.X(), positionStop.Y(), positionStop.Z()); + math_utils::Vector3D momStart(mTrackData.mMomentumStart.Px(), mTrackData.mMomentumStart.Py(), mTrackData.mMomentumStart.Pz()); + + Hit* p = addHit(trackId, detId, posStart, posStop, + momStart, mTrackData.mMomentumStart.E(), + positionStop.T(), mTrackData.mEnergyLoss, particlePdg); + stack->addHit(GetDetId()); + } else { + return false; // do nothing more + } + return true; +} + +o2::fd3::Hit* Detector::addHit(int trackId, unsigned int detId, + const math_utils::Point3D& startPos, + const math_utils::Point3D& endPos, + const math_utils::Vector3D& startMom, + double startE, + double endTime, + double eLoss, + int particlePdg) +{ + mHits->emplace_back(trackId, detId, startPos, + endPos, startMom, startE, endTime, eLoss, particlePdg); + return &(mHits->back()); +} + +void Detector::ConstructGeometry() +{ + createMaterials(); + buildModules(); +} + +void Detector::EndOfEvent() +{ + Reset(); +} + +void Detector::Register() +{ + // This will create a branch in the output tree called Hit, setting the last + // parameter to kFALSE means that this collection will not be written to the file, + // it will exist only during the simulation + + if (FairRootManager::Instance()) { + FairRootManager::Instance()->RegisterAny(addNameTo("Hit").data(), mHits, kTRUE); + } +} + +void Detector::Reset() +{ + if (!o2::utils::ShmManager::Instance().isOperational()) { + mHits->clear(); + } +} + +void Detector::createMaterials() +{ + float density, as[11], zs[11], ws[11]; + double radLength, absLength, a_ad, z_ad; + int id; + + // EJ-204 scintillator, based on polyvinyltoluene + const int nScint = 2; + float aScint[nScint] = {1.00784, 12.0107}; + float zScint[nScint] = {1, 6}; + float wScint[nScint] = {0.07085, 0.92915}; // based on EJ-204 datasheet: n_atoms/cm3 + const float dScint = 1.023; + + // Aluminium + Float_t aAlu = 26.981; + Float_t zAlu = 13; + Float_t dAlu = 2.7; + + int matId = 0; // tmp material id number + const int unsens = 0, sens = 1; // sensitive or unsensitive medium + // + int fieldType = 3; // Field type + float maxField = 5.0; // Field max. + + float tmaxfd3 = -10.0; // max deflection angle due to magnetic field in one step + float stemax = 0.1; // max step allowed [cm] + float deemax = 1.0; // maximum fractional energy loss in one step 0GetVolume("cave"); + + if (!vCave) { + LOG(fatal) << "Could not find the top volume!"; + } + + TGeoVolumeAssembly* vFD3A = buildModuleA(); + TGeoVolumeAssembly* vFD3C = buildModuleC(); + + vCave->AddNode(vFD3A, 1, new TGeoTranslation(0., 0., mZA)); + vCave->AddNode(vFD3C, 2, new TGeoTranslation(0., 0., mZC)); +} + +TGeoVolumeAssembly* Detector::buildModuleA() +{ + TGeoVolumeAssembly* mod = new TGeoVolumeAssembly("FD3A"); + + const TGeoMedium* medium = gGeoManager->GetMedium("FD3_Scintillator"); + + float dphiDeg = 360. / mNumberOfSectors; + + for (int ir = 0; ir < mNumberOfRingsA; ir++) { + std::string rName = "fd3_ring" + std::to_string(ir + 1); + TGeoVolumeAssembly* ring = new TGeoVolumeAssembly(rName.c_str()); + float rmin = mRingSizesA[ir]; + float rmax = mRingSizesA[ir + 1]; + LOG(info) << "ring" << ir << ": from " << rmin << " to " << rmax; + for (int ic = 0; ic < mNumberOfSectors; ic++) { + int cellId = ic + mNumberOfSectors * ir; + std::string nodeName = "fd3_node" + std::to_string(cellId); + float phimin = dphiDeg * ic; + float phimax = dphiDeg * (ic + 1); + auto tbs = new TGeoTubeSeg("tbs", rmin, rmax, mDzScint, phimin, phimax); + auto nod = new TGeoVolume(nodeName.c_str(), tbs, medium); + if ((ir + ic) % 2 == 0) { + nod->SetLineColor(kRed); + } else { + nod->SetLineColor(kRed - 7); + } + ring->AddNode(nod, cellId); + } + mod->AddNode(ring, ir + 1); + } + + // Aluminium plates on one or both sides of the A side module + if (mPlateBehindA || mFullContainer) { + LOG(info) << "adding container on A side"; + auto pmed = (TGeoMedium*)gGeoManager->GetMedium("FD3_Aluminium"); + auto pvol = new TGeoTube("pvol_fd3a", mRingSizesA[0], mRingSizesA[mNumberOfRingsA], mDzPlate); + auto pnod1 = new TGeoVolume("pnod1_FD3A", pvol, pmed); + double dpz = 2. + mDzPlate / 2; + mod->AddNode(pnod1, 1, new TGeoTranslation(0, 0, dpz)); + + if (mFullContainer) { + auto pnod2 = new TGeoVolume("pnod2_FD3A", pvol, pmed); + mod->AddNode(pnod2, 1, new TGeoTranslation(0, 0, -dpz)); + } + } + return mod; +} + +TGeoVolumeAssembly* Detector::buildModuleC() +{ + TGeoVolumeAssembly* mod = new TGeoVolumeAssembly("FD3C"); + + const TGeoMedium* medium = gGeoManager->GetMedium("FD3_Scintillator"); + + float dphiDeg = 360. / mNumberOfSectors; + + for (int ir = 0; ir < mNumberOfRingsC; ir++) { + std::string rName = "fd3_ring" + std::to_string(ir + 1 + mNumberOfRingsA); + TGeoVolumeAssembly* ring = new TGeoVolumeAssembly(rName.c_str()); + float rmin = mRingSizesC[ir]; + float rmax = mRingSizesC[ir + 1]; + LOG(info) << "ring" << ir + mNumberOfRingsA << ": from " << rmin << " to " << rmax; + for (int ic = 0; ic < mNumberOfSectors; ic++) { + int cellId = ic + mNumberOfSectors * (ir + mNumberOfRingsA); + std::string nodeName = "fd3_node" + std::to_string(cellId); + float phimin = dphiDeg * ic; + float phimax = dphiDeg * (ic + 1); + auto tbs = new TGeoTubeSeg("tbs", rmin, rmax, mDzScint, phimin, phimax); + auto nod = new TGeoVolume(nodeName.c_str(), tbs, medium); + if ((ir + ic) % 2 == 0) { + nod->SetLineColor(kBlue); + } else { + nod->SetLineColor(kBlue - 7); + } + ring->AddNode(nod, cellId); + } + mod->AddNode(ring, ir + 1); + } + + // Aluminium plates on both sides of the C side module + if (mFullContainer) { + LOG(info) << "adding container on C side"; + auto pmed = (TGeoMedium*)gGeoManager->GetMedium("FD3_Aluminium"); + auto pvol = new TGeoTube("pvol_fd3c", mRingSizesC[0], mRingSizesC[mNumberOfRingsC], mDzPlate); + auto pnod1 = new TGeoVolume("pnod1_FD3C", pvol, pmed); + auto pnod2 = new TGeoVolume("pnod2_FD3C", pvol, pmed); + double dpz = mDzScint / 2 + mDzPlate / 2; + + mod->AddNode(pnod1, 1, new TGeoTranslation(0, 0, dpz)); + mod->AddNode(pnod2, 2, new TGeoTranslation(0, 0, -dpz)); + } + + return mod; +} + +void Detector::defineSensitiveVolumes() +{ + LOG(info) << "Adding FD3 Sentitive Volumes"; + + TGeoVolume* v; + TString volumeName; + + int nCellsA = mNumberOfRingsA * mNumberOfSectors; + int nCellsC = mNumberOfRingsC * mNumberOfSectors; + + LOG(info) << "number of A rings = " << mNumberOfRingsA << " number of cells = " << nCellsA; + LOG(info) << "number of C rings = " << mNumberOfRingsC << " number of cells = " << nCellsC; + + for (int iv = 0; iv < nCellsA + nCellsC; iv++) { + volumeName = "fd3_node" + std::to_string(iv); + v = gGeoManager->GetVolume(volumeName); + LOG(info) << "Adding sensitive volume => " << v->GetName(); + AddSensitiveVolume(v); + } +} + +float Detector::ringSize(float z, float eta) +{ + return z * TMath::Tan(2 * TMath::ATan(TMath::Exp(-eta))); +} + +ClassImp(o2::fd3::Detector); diff --git a/DataFormats/Detectors/EMCAL/src/EMCALChannelData.cxx b/Detectors/Upgrades/ALICE3/FD3/simulation/src/FD3SimulationLinkDef.h similarity index 70% rename from DataFormats/Detectors/EMCAL/src/EMCALChannelData.cxx rename to Detectors/Upgrades/ALICE3/FD3/simulation/src/FD3SimulationLinkDef.h index 8affa29259f7a..83df03490ebd7 100644 --- a/DataFormats/Detectors/EMCAL/src/EMCALChannelData.cxx +++ b/Detectors/Upgrades/ALICE3/FD3/simulation/src/FD3SimulationLinkDef.h @@ -9,11 +9,13 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file EMCALChannelData.cxx -/// \brief Class to store the data format for calibraton of the EMCal +#ifdef __CLING__ -#include "DataFormatsEMCAL/EMCALChannelData.h" +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; -using namespace o2::dataformats; +#pragma link C++ class o2::fd3::Detector + ; +#pragma link C++ class o2::base::DetImpl < o2::fd3::Detector> + ; -ClassImp(o2::dataformats::EMCALChannelData; +#endif diff --git a/Detectors/Upgrades/ALICE3/FT3/README.md b/Detectors/Upgrades/ALICE3/FT3/README.md index 71cb7a6e63bb9..c11352607db85 100644 --- a/Detectors/Upgrades/ALICE3/FT3/README.md +++ b/Detectors/Upgrades/ALICE3/FT3/README.md @@ -6,5 +6,29 @@ This is top page for the FT3 detector documentation. +## Specific detector setup + + +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 | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `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, 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=kSegmented; FT3Base.drawReferenceCircles=true" +``` + diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h index b286aa068611c..bb2969b69dc79 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/FT3BaseParam.h @@ -19,20 +19,18 @@ namespace o2 { namespace ft3 { - -// ** -// ** Parameters for FT3 base configuration -// ** - -enum FT3Geometry { - Default = 0, - Telescope = 1 +// Parameters for FT3 (ML and OT disks) +enum eFT3Layout { + kCylindrical = 0, + kTrapezoidal, + kSegmented, + kSegmentedStave, + kSegmentedStaveOTOnly }; - struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { // Geometry Builder parameters - - Int_t geoModel = FT3Geometry::Default; + eFT3Layout layoutFT3 = kSegmentedStaveOTOnly; + int nTrapezoidalSegments = 32; // for the simple trapezoidal disks // FT3Geometry::Telescope parameters Int_t nLayers = 10; @@ -42,8 +40,15 @@ struct FT3BaseParam : public o2::conf::ConfigurableParamHelper { Float_t etaOut = 1.5; Float_t Layerx2X0 = 0.01; - // FT3Geometry::External file - std::string configFile = ""; // Overrides geoModel parameter when provided + // override values from FT3ModuleConstants, inner and outer + bool cutStavesOnNominalRadius_inner = true; + bool cutStavesOnNominalRadius_outer = true; + + // What to place over x=0 line in case of full outer-outer stave: Gap or Sensor + bool placeSensorInMiddleOfStave = false; + + // Draw reference circles at inner and outer radius of stave layer, for visualisation + bool drawReferenceCircles = false; O2ParamDef(FT3BaseParam, "FT3Base"); }; diff --git a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h index bb22af7ad7f9d..1941b543579db 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/FT3/base/include/FT3Base/GeometryTGeo.h @@ -94,6 +94,7 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static const char* getFT3LayerPattern() { return sLayerName.c_str(); } static const char* getFT3ChipPattern() { return sChipName.c_str(); } static const char* getFT3SensorPattern() { return sSensorName.c_str(); } + static const char* getFT3PassivePattern() { return sPassiveName.c_str(); } static const char* composeSymNameFT3(Int_t d) { return Form("%s_%d", o2::detectors::DetID(o2::detectors::DetID::FT3).getName(), d); } static const char* composeSymNameLayer(Int_t d, Int_t lr); @@ -101,15 +102,12 @@ class GeometryTGeo : public o2::itsmft::GeometryTGeo static const char* composeSymNameSensor(Int_t d, Int_t lr); protected: - static constexpr int MAXLAYERS = 15; ///< max number of active layers - - Int_t mNumberOfLayers; ///< number of layers static std::string sInnerVolumeName; ///< Mother inner volume name static std::string sVolumeName; ///< Mother volume name static std::string sLayerName; ///< Layer name static std::string sChipName; ///< Chip name - static std::string sSensorName; ///< Sensor name + static std::string sPassiveName; ///< Passive material name private: static std::unique_ptr sInstance; ///< singletone instance diff --git a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx b/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx index 6833c4c3d1b89..73b2bc9b94eb8 100644 --- a/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/base/src/GeometryTGeo.cxx @@ -52,8 +52,9 @@ std::unique_ptr GeometryTGeo::sInstance; std::string GeometryTGeo::sVolumeName = "FT3V"; ///< Mother volume name std::string GeometryTGeo::sInnerVolumeName = "FT3Inner"; ///< Mother inner volume name std::string GeometryTGeo::sLayerName = "FT3Layer"; ///< Layer name -std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Sensor name +std::string GeometryTGeo::sChipName = "FT3Chip"; ///< Chip name std::string GeometryTGeo::sSensorName = "FT3Sensor"; ///< Sensor name +std::string GeometryTGeo::sPassiveName = "FT3Passive"; ///< Passive material name //__________________________________________________________________________ GeometryTGeo::GeometryTGeo(bool build, int loadTrans) : o2::itsmft::GeometryTGeo(DetID::FT3) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt index 89f8c23797fac..23414d4ae7269 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/CMakeLists.txt @@ -10,14 +10,18 @@ # or submit itself to any jurisdiction. o2_add_library(FT3Simulation - SOURCES src/FT3Layer.cxx + SOURCES + src/FT3Module.cxx + src/FT3Layer.cxx src/Detector.cxx PUBLIC_LINK_LIBRARIES O2::FT3Base O2::ITSMFTSimulation ROOT::Physics) o2_target_root_dictionary(FT3Simulation - HEADERS include/FT3Simulation/Detector.h + HEADERS + include/FT3Simulation/FT3Module.h + include/FT3Simulation/Detector.h include/FT3Simulation/FT3Layer.h) o2_data_file(COPY data DESTINATION Detectors/FT3/simulation) diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h index a88ea5a351ad2..361d94463ef56 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/Detector.h @@ -15,17 +15,20 @@ #ifndef ALICEO2_FT3_DETECTOR_H_ #define ALICEO2_FT3_DETECTOR_H_ -#include // for vector -#include "DetectorsBase/GeometryManager.h" // for getSensID +#include "Rtypes.h" // for Int_t, Double_t, Float_t, Bool_t, etc + #include "DetectorsBase/Detector.h" // for Detector +#include "DetectorsBase/GeometryManager.h" // for getSensID #include "DetectorsCommonDataFormats/DetID.h" // for Detector #include "ITSMFTSimulation/Hit.h" // for Hit -#include "Rtypes.h" // for Int_t, Double_t, Float_t, Bool_t, etc -#include "TArrayD.h" // for TArrayD -#include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) -#include "TLorentzVector.h" // for TLorentzVector -#include "TVector3.h" // for TVector3 -#include "FT3Base/FT3BaseParam.h" + +#include "TArrayD.h" // for TArrayD +#include "TGeoManager.h" // for gGeoManager, TGeoManager (ptr only) +#include "TLorentzVector.h" // for TLorentzVector +#include "TVector3.h" // for TVector3 + +#include +#include class FairVolume; class TGeoVolume; @@ -34,25 +37,10 @@ class TParticle; class TString; -namespace o2 -{ -namespace ft3 +namespace o2::ft3 { class GeometryTGeo; -} -} // namespace o2 -namespace o2 -{ -namespace ft3 -{ -class FT3Layer; -} -} // namespace o2 - -namespace o2 -{ -namespace ft3 -{ +class FT3BaseParam; class FT3Layer; class Detector : public o2::base::DetImpl @@ -108,24 +96,27 @@ class Detector : public o2::base::DetImpl void PostTrack() override { ; } void PreTrack() override { ; } + static constexpr int IdxForwardDisks = 0; + static constexpr int IdxBackwardDisks = 1; /// Returns the number of layers - Int_t getNumberOfLayers() const { return mNumberOfLayers; } + size_t getNumberOfLayers() const + { + if (mLayerName[IdxBackwardDisks].size() != mLayerName[IdxForwardDisks].size()) { + LOG(fatal) << "Number of layers in the two directions are different! Returning 0."; + } + return mLayerName[IdxBackwardDisks].size(); + } void buildBasicFT3(const FT3BaseParam& param); void buildFT3V1(); void buildFT3V3b(); void buildFT3Scoping(); void buildFT3NewVacuumVessel(); - void buildFT3FromFile(std::string); - - GeometryTGeo* mGeometryTGeo; //! access to geometry details - - void exportLayout(); + void buildFT3ScopingV3(); protected: - std::vector mLayerID; - std::vector> mLayerName; - Int_t mNumberOfLayers; + std::array, 2> mLayerName; // Two sets of layer names, one per direction (forward/backward) + std::unordered_map mActiveSensorMap; private: /// this is transient data about track passing the sensor @@ -153,16 +144,15 @@ class Detector : public o2::base::DetImpl Detector& operator=(const Detector&); - std::vector> mLayers; - bool mIsPipeActivated = true; //! If Alice 3 pipe is present append inner disks to vacuum volume to avoid overlaps + std::array, 2> mLayers; // Two sets of layers, one per direction (forward/backward) + bool mIsPipeActivated = true; //! If Alice 3 pipe is present append inner disks to vacuum volume to avoid overlaps template friend class o2::base::DetImpl; - ClassDefOverride(Detector, 1); + ClassDefOverride(Detector, 2); }; -} // namespace ft3 -} // namespace o2 +} // namespace o2::ft3 #ifdef USESHM namespace o2 diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h index 7159f2a6d1d9f..282f8fd274ec0 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Layer.h @@ -18,6 +18,7 @@ #include // for gGeoManager #include "Rtypes.h" // for Double_t, Int_t, Bool_t, etc #include "FT3Simulation/Detector.h" // for Detector, Detector::Model +#include "FT3Simulation/FT3Module.h" class TGeoVolume; @@ -35,7 +36,7 @@ class FT3Layer : public TObject FT3Layer() = default; // Sample layer constructor - FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerName, Float_t z, Float_t rIn, Float_t rOut, Float_t Layerx2X0); + FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerName, Float_t z, Float_t rIn, Float_t rOut, Float_t Layerx2X0, bool partOfMiddleLayers); /// Copy constructor FT3Layer(const FT3Layer&) = default; @@ -50,6 +51,7 @@ class FT3Layer : public TObject auto getInnerRadius() const { return mInnerRadius; } auto getOuterRadius() const { return mOuterRadius; } auto getDirection() const { return mDirection; } + bool getIsInMiddleLayer() const { return mIsMiddleLayer; } auto getZ() const { return mZ; } auto getx2X0() const { return mx2X0; } @@ -57,15 +59,36 @@ class FT3Layer : public TObject /// \param motherVolume the TGeoVolume owing the volume structure virtual void createLayer(TGeoVolume* motherVolume); + static void initialize_mat(); + + // create layer for disk support + void createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName); + void createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName); + void createReferenceCircles(TGeoVolume* motherVolume, const std::string& name); + + static TGeoMaterial* carbonFiberMat; + static TGeoMedium* medCarbonFiber; + + static TGeoMaterial* kaptonMat; + static TGeoMedium* kaptonMed; + + static TGeoMaterial* waterMat; + static TGeoMedium* waterMed; + + static TGeoMaterial* foamMat; + static TGeoMedium* medFoam; + private: - Int_t mLayerNumber = -1; ///< Current layer number - Int_t mDirection; ///< Layer direction 0=Forward 1 = Backward - std::string mLayerName; ///< Current layer name - Double_t mInnerRadius; ///< Inner radius of this layer - Double_t mOuterRadius; ///< Outer radius of this layer - Double_t mZ; ///< Z position of the layer - Double_t mChipThickness; ///< Chip thickness - Double_t mx2X0; ///< Layer material budget x/X0 + Int_t mLayerNumber = -1; ///< Current layer number + Int_t mDirection; ///< Layer direction 0=Forward 1 = Backward + bool mIsMiddleLayer = true; ///< Wether this layer is part of the middle layers + std::string mLayerName; ///< Current layer name + Double_t mInnerRadius; ///< Inner radius of this layer + Double_t mOuterRadius; ///< Outer radius of this layer + Double_t mZ; ///< Z position of the layer + Double_t mChipThickness; ///< Chip thickness + Double_t mSensorThickness; ///< Sensor thickness + Double_t mx2X0; ///< Layer material budget x/X0 ClassDefOverride(FT3Layer, 0); // ALICE 3 EndCaps geometry }; diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h new file mode 100644 index 0000000000000..1311c6a4ff1b5 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3Module.h @@ -0,0 +1,106 @@ +// 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 FT3Module.h +/// \brief Definition of the FT3Module class + +#ifndef FT3MODULE_H +#define FT3MODULE_H + +#include +#include +#include + +#include "FT3Simulation/FT3ModuleConstants.h" + +// define types for y positions, second element is the stack height +using PositionType = std::pair; +using PositionTypes = std::vector; +using PosNegPositionTypes = std::pair; +// define type of the y position range: First pair is (min, max) for positive y +using PositionRangeType = std::pair, std::pair>; +namespace Constants = o2::ft3::ModuleConstants; + +class FT3Module +{ + + public: + static void initialize_materials(); + static TGeoMaterial* siliconMat; + static TGeoMedium* siliconMed; + static TGeoMaterial* copperMat; + static TGeoMedium* copperMed; + static TGeoMaterial* kaptonMat; + static TGeoMedium* kaptonMed; + static TGeoMaterial* epoxyMat; + static TGeoMedium* epoxyMed; + static TGeoMaterial* AluminumMat; + static TGeoMedium* AluminumMed; + static TGeoMaterial* carbonFiberMat; + static TGeoMedium* carbonFiberMed; + + const char* mDetName; + + static void createModule( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double overlap, const std::string& face, + const std::string& layout_type, TGeoVolume* motherVolume); + + void createModule_staveGeo( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); + + private: + static void 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); + + void create_layout_staveGeo( + double mZ, int layerNumber, int direction, double Rin, + double Rout, double z_offset_local, const Constants::StaveConfig& staveConfig, + TGeoVolume* motherVolume); + + // Helper functions + void fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, PositionRangeType y_range, + std::pair& absAllowedYRange); + void 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); + void 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); + + void 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); + + void add2x1CopperVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); + + void add2x1KaptonVolume( + TGeoVolume* motherVolume, int layerNumber, int direction, unsigned stave_idx, + unsigned* volume_count, double x_mid, double y_mid, double z_mid); + + void 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); +}; + +#endif // FT3MODULE_H diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h new file mode 100644 index 0000000000000..5c976bc3bd902 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/include/FT3Simulation/FT3ModuleConstants.h @@ -0,0 +1,206 @@ +// 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 FT3ModuleConstants.h +/// \brief Definition of various constants for tiling the modules of sensors + +#ifndef FT3MODULECONSTANTS_H +#define FT3MODULECONSTANTS_H + +#include +#include +#include +#include + +namespace o2::ft3::ModuleConstants +{ +/* CURRENT STATUS: + * 25x32mm sensors, 2mm inactive on one side + * Most granular layout is 2x1 sensors, where the one on the right has the inactive region + * on the right, and the one on the left has the inactive region on the left. + * When stacking 2x1 modules, there is a 0.2mm gap between them. By default, we assume this + * gap to be ABOVE the most recently placed module. + * + * |<- 25mm ->|<- 25mm ->| + * _______________________ + * ----------------------- 0.2mm gap above + * | | | | | + * | | | | | + * | | | | | + * | | | | | 32mm sensor height + * | | | | | + * | | | | | + * ------------------------ + * + */ +// First set all layout constants for the rest of the function +const double single_sensor_width = 2.5; +const double single_sensor_height = 2.9; +const double inactive_width = 0.2; +const double sensor2x1_gap = 0.02; +const double stackGap = sensor2x1_gap; // gap between 2xN module stacks + +const double active_width = single_sensor_width - inactive_width; +const double active_height = single_sensor_height; + +const double sensor2x1_width = 2 * single_sensor_width; +const double sensor2x1_active_width = 2 * active_width; +const double sensor2x1_height = single_sensor_height; +const std::vector kSensorsPerStack = {4, 2, 1}; +inline const double getStackHeight(unsigned nSensorsPerStack) +{ + return nSensorsPerStack * sensor2x1_height + + (nSensorsPerStack - 1) * sensor2x1_gap; +} + +// small helper function to get 1-indexed stave ID, counting from the middle outwards, +// with negative IDs on the left and positive IDs on the right +inline const int staveIdxToID(int staveIdx, unsigned nStavesPerDisc) +{ + unsigned nStavesOneSide = nStavesPerDisc / 2; + bool isRight = staveIdx >= nStavesOneSide; + return staveIdx - nStavesOneSide + isRight; +} + +// material properties +const double siliconThickness = 0.01; +const double copperThickness = 0.006; +const double kaptonThickness = 0.03; +const double epoxyThickness = 0.0012; + +const double effectiveCarbonThickness_Stave = 0.02; // foam + shell +const double staveOpeningAngle = 60 * TMath::DegToRad(); +const double sinTheta = TMath::Sin(staveOpeningAngle / 2); +const double alpha = TMath::Pi() / 2 - staveOpeningAngle / 2; // bottom angles +const double staveSensorGap = 0.1; // 2mm padding on each side when sensor is glued +const double staveTriangleHeight = (sensor2x1_width + 2 * staveSensorGap) / 2.0 / tan(staveOpeningAngle / 2.0); +/* + * Now describe the offset of every other stave in z to avoid overlaps + * ______ ______ + * \ /______\ / | <-- z_offsetStave + * \ / \ / \ / + * \/ \ / \/ + * \/ + */ +// If midpoint spacing becomes non constant, this becomes a function +// TODO: add some tolerance to avoid overlaps? +inline const double z_offsetStave(double x_midpoint_spacing) +{ + return staveTriangleHeight * + (2 - x_midpoint_spacing / (sensor2x1_width / 2 + staveSensorGap)); +} + +const int SiColor = kGreen; +const int SiInactiveColor = kRed; +const int glueColor = kBlue; +const int CuColor = kOrange; +const int kaptonColor = kYellow; +const int carbonFiberColor = kGray + 1; + +// Struct for stave position configuration (varies between IT/OT) +struct StaveConfig { + /* + * Constants for staves are written for both positive + * and negative x even though they are just mirrored now, + * because there might be design changes in the future + * that require a non-mirrored layout, making it easier to + * change here if so required, even though it looks uglier now. + * + * The second element in the mapping pair is whether the stave + * with a certain ID should be mirrored around the x-axis. + */ + // map from Stave ID (1-indexed from other documents) to midpoint + // Do NOT add any zero midpoints, this is taken off separately + const std::map>& staveID_to_y_midpoint; + // lengths of staves, their midpoint, and their face + const std::vector& y_lengths; + const std::vector& x_midpoints; + double x_midpoint_spacing; + // which side of the disc do we place the stave? + // kSegmentedStave: staggering staves in z (see z_offsetStave) + // accessed via stave index, NOT stave ID + const std::vector& staveOnFront; +}; + +namespace OT_StavePositions +{ +const std::map> staveID_to_y_midpoint = { + {-2, {39.0, true}}, + {-1, {41.4, true}}, + {1, {41.4, true}}, + {2, {39.0, true}}}; +const std::vector y_lengths = { + 52.8, 66.0, 79.2, 92.4, 99.0, 105.6, 118.8, 118.8, + 128.7, 132.0, 132.0, 138.6, 138.6, 56.1, 52.8, + 52.8, 56.1, 138.6, 138.6, 132.0, 132.0, 128.7, + 118.8, 118.8, 105.6, 99.0, 92.4, 79.2, 66.0, 52.8}; +const std::vector x_midpoints = { + -65.25, -60.75, -56.25, -51.75, -47.25, -42.75, -38.25, // L + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75, // R + 38.25, 42.75, 47.25, 51.75, 56.25, 60.75, 65.25 // R +}; +const double x_midpoint_spacing = 4.5; // assume constant for now +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, // L + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 // R +}; +} // namespace OT_StavePositions + +namespace ML_StavePositions +{ +// Use prelim numbers for now, these will change! TODO +const std::map> staveID_to_y_midpoint = { + {-3, {19.1, true}}, + {-2, {21.8, true}}, + {-1, {22.5, true}}, + {1, {22.5, true}}, + {2, {21.8, true}}, + {3, {19.1, true}}}; +const std::vector y_lengths = { + 30.5, 44.5, 53.6, 60.0, 64.6, 29.5, 25.8, 25.0, + 25.0, 25.8, 29.5, 64.6, 60.0, 53.6, 44.5, 30.5}; +const std::vector x_midpoints = { + -33.75, -29.25, -24.75, -20.25, -15.75, -11.25, -6.75, -2.25, // L + 2.25, 6.75, 11.25, 15.75, 20.25, 24.75, 29.25, 33.75 // R +}; +const double x_midpoint_spacing = 4.5; +const std::vector staveOnFront = + { + 1, 0, 1, 0, 1, 0, 1, 0, // L + 1, 0, 1, 0, 1, 0, 1, 0 // R +}; +} // namespace ML_StavePositions + +// Get stave configuration based on tracker type +inline StaveConfig getStaveConfig(bool isInnerDisk) +{ + if (isInnerDisk) { + return StaveConfig{ + ML_StavePositions::staveID_to_y_midpoint, + ML_StavePositions::y_lengths, + ML_StavePositions::x_midpoints, + ML_StavePositions::x_midpoint_spacing, + ML_StavePositions::staveOnFront}; + } else { + return StaveConfig{ + OT_StavePositions::staveID_to_y_midpoint, + OT_StavePositions::y_lengths, + OT_StavePositions::x_midpoints, + OT_StavePositions::x_midpoint_spacing, + OT_StavePositions::staveOnFront}; + } +} + +} // namespace o2::ft3::ModuleConstants + +#endif // FT3MODULECONSTANTS_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx index ce132fdb33cd3..1f9f95c1914dd 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/Detector.cxx @@ -12,34 +12,38 @@ /// \file Detector.cxx /// \brief Implementation of the Detector class -#include "ITSMFTSimulation/Hit.h" -#include "FT3Base/GeometryTGeo.h" #include "FT3Simulation/Detector.h" -#include "FT3Simulation/FT3Layer.h" -#include "FT3Base/FT3BaseParam.h" #include "DetectorsBase/Stack.h" +#include "ITSMFTSimulation/Hit.h" #include "SimulationDataFormat/TrackReference.h" +#include "FT3Base/FT3BaseParam.h" +#include "FT3Base/GeometryTGeo.h" +#include "FT3Simulation/FT3Layer.h" + // FairRoot includes -#include "FairDetector.h" // for FairDetector -#include // for LOG, LOG_IF -#include "FairRootManager.h" // for FairRootManager -#include "FairRun.h" // for FairRun -#include "FairRuntimeDb.h" // for FairRuntimeDb -#include "FairVolume.h" // for FairVolume +#include "FairDetector.h" // for FairDetector +#include "FairRootManager.h" // for FairRootManager #include "FairRootManager.h" +#include "FairRun.h" // for FairRun +#include "FairRuntimeDb.h" // for FairRuntimeDb +#include "FairVolume.h" // for FairVolume #include "TGeoManager.h" // for TGeoManager, gGeoManager -#include "TGeoTube.h" // for TGeoTube #include "TGeoPcon.h" // for TGeoPcon +#include "TGeoTube.h" // for TGeoTube #include "TGeoVolume.h" // for TGeoVolume, TGeoVolumeAssembly #include "TString.h" // for TString, operator+ #include "TVirtualMC.h" // for gMC, TVirtualMC #include "TVirtualMCStack.h" // for TVirtualMCStack +#include // for LOG, LOG_IF + #include // for NULL, snprintf +#define MAX_SENSORS 2000 + class FairModule; class TGeoMedium; @@ -57,96 +61,6 @@ Detector::Detector() { } -//_________________________________________________________________________________________________ -void Detector::buildFT3FromFile(std::string configFileName) -{ - // Geometry description from file. One line per disk - // z_layer r_in r_out Layerx2X0 - // This simple file reader is not failproof. Do not add empty lines! - - /* - # Sample FT3 configuration - # z_layer r_in r_out Layerx2X0 - -45.3 2.5 9.26 0.0042 - -46.7 2.5 9.26 0.0042 - -48.6 2.5 9.8 0.0042 - -50.0 2.5 9.8 0.0042 - -52.4 2.5 10.43 0.0042 - -53.8 2.5 10.43 0.0042 - -67.7 3.82 13.01 0.0042 - -69.1 3.82 13.01 0.0042 - -76.1 3.92 14.35 0.0042 - -77.5 3.92 14.35 0.0042 - */ - - mLayerName.clear(); - mLayers.clear(); - mLayerID.clear(); - mLayerName.resize(1); - mLayers.resize(1); - - LOG(info) << "Building FT3 Detector: From file"; - LOG(info) << " FT3 detector configuration: " << configFileName; - std::ifstream ifs(configFileName.c_str()); - if (!ifs.good()) { - LOG(fatal) << " Invalid FT3Base.configFile!"; - } - std::string tempstr; - float z_layer, r_in, r_out, Layerx2X0; - char delimiter; - int layerNumber = 0; - while (std::getline(ifs, tempstr)) { - if (tempstr[0] == '#') { - LOG(info) << " Comment: " << tempstr; - continue; - } - std::istringstream iss(tempstr); - iss >> z_layer; - iss >> r_in; - iss >> r_out; - iss >> Layerx2X0; - - int direction = 1; // Forwards - if (z_layer < 0) { - // Backwards - direction = 0; - } - - std::string directionName = std::to_string(direction); - std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); - mLayerName[0].push_back(layerName); - LOG(info) << "Adding Layer " << layerName << " at z = " << z_layer << " ; direction = " << direction << " ; r_in = " << r_in << " ; r_out = " << r_out << " x/X0 = " << Layerx2X0; - auto& thisLayer = mLayers[0].emplace_back(direction, layerNumber, layerName, z_layer, r_in, r_out, Layerx2X0); - layerNumber++; - } - - mNumberOfLayers = layerNumber; - LOG(info) << " Loaded FT3 Detector with " << mNumberOfLayers << " layers"; -} - -//_________________________________________________________________________________________________ -void Detector::exportLayout() -{ - // Export FT3 Layout description to file. One line per disk - // z_layer r_in r_out Layerx2X0 - - std::string configFileName = "FT3_layout.cfg"; - - LOG(info) << "Exporting FT3 Detector layout to " << configFileName; - - std::ofstream fOut(configFileName.c_str(), std::ios::out); - if (!fOut) { - printf("Cannot open file\n"); - return; - } - fOut << "# z_layer r_in r_out Layerx2X0" << std::endl; - for (auto layers_dir : mLayers) { - for (auto layer : layers_dir) { - fOut << layer.getZ() << " " << layer.getInnerRadius() << " " << layer.getOuterRadius() << " " << layer.getx2X0() << std::endl; - } - } -} - //_________________________________________________________________________________________________ void Detector::buildBasicFT3(const FT3BaseParam& param) { @@ -155,28 +69,26 @@ void Detector::buildBasicFT3(const FT3BaseParam& param) LOG(info) << "Building FT3 Detector: Conical Telescope"; - auto z_first = param.z0; - auto z_length = param.zLength; - auto etaIn = param.etaIn; - auto etaOut = param.etaOut; - auto Layerx2X0 = param.Layerx2X0; - mNumberOfLayers = param.nLayers; - mLayerName.resize(2); - mLayerName[0].resize(mNumberOfLayers); - mLayerName[1].resize(mNumberOfLayers); - mLayerID.clear(); - mLayers.resize(2); - - for (int direction : {0, 1}) { - for (int layerNumber = 0; layerNumber < mNumberOfLayers; layerNumber++) { - std::string layerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(layerNumber + mNumberOfLayers * direction); + const int numberOfLayers = param.nLayers; + const auto z_first = param.z0; + const auto z_length = param.zLength; + const auto etaIn = param.etaIn; + const auto etaOut = param.etaOut; + const auto Layerx2X0 = param.Layerx2X0; + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + std::string layerName = GeometryTGeo::getFT3LayerPattern() + std::to_string(layerNumber + numberOfLayers * direction); mLayerName[direction][layerNumber] = layerName; // Adds evenly spaced layers - float layerZ = z_first + (layerNumber * z_length / (mNumberOfLayers - 1)) * std::copysign(1, z_first); - float rIn = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaIn)))); - float rOut = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaOut)))); - auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, layerZ, rIn, rOut, Layerx2X0); + const float layerZ = z_first + (layerNumber * z_length / numberOfLayers) * std::copysign(1, z_first); + const float rIn = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaIn)))); + const float rOut = std::abs(layerZ * std::tan(2.f * std::atan(std::exp(-etaOut)))); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, layerZ, rIn, rOut, Layerx2X0, isMiddleLayer); } } } @@ -189,10 +101,10 @@ void Detector::buildFT3V1() LOG(info) << "Building FT3 Detector: V1"; - mNumberOfLayers = 10; - float sensorThickness = 30.e-4; - float layersx2X0 = 1.e-2; - std::vector> layersConfig{ + const int numberOfLayers = 10; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfig{ {26., .5, 3., 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} {30., .5, 3., 0.1f * layersx2X0}, {34., .5, 3., 0.1f * layersx2X0}, @@ -204,14 +116,11 @@ void Detector::buildFT3V1() {220., 3.5, 80.f, layersx2X0}, {279., 3.5, 80.f, layersx2X0}}; - mLayerName.resize(2); - mLayerName[0].resize(mNumberOfLayers); - mLayerName[1].resize(mNumberOfLayers); - mLayerID.clear(); - mLayers.resize(2); + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); - for (auto direction : {0, 1}) { - for (int layerNumber = 0; layerNumber < mNumberOfLayers; layerNumber++) { + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { std::string directionName = std::to_string(direction); std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); mLayerName[direction][layerNumber] = layerName; @@ -223,7 +132,8 @@ void Detector::buildFT3V1() LOG(info) << "Adding Layer " << layerName << " at z = " << z; // Add layers - auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); } } } @@ -236,10 +146,10 @@ void Detector::buildFT3V3b() LOG(info) << "Building FT3 Detector: V3b"; - mNumberOfLayers = 12; + const int numberOfLayers = 12; float sensorThickness = 30.e-4; float layersx2X0 = 1.e-2; - std::vector> layersConfig{ + std::vector> layersConfig{ {26., .5, 3., 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} {30., .5, 3., 0.1f * layersx2X0}, {34., .5, 3., 0.1f * layersx2X0}, @@ -253,14 +163,11 @@ void Detector::buildFT3V3b() {340., 12.5, 80.f, layersx2X0}, {400., 14.7, 80.f, layersx2X0}}; - mLayerName.resize(2); - mLayerName[0].resize(mNumberOfLayers); - mLayerName[1].resize(mNumberOfLayers); - mLayerID.clear(); - mLayers.resize(2); + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); - for (auto direction : {0, 1}) { - for (int layerNumber = 0; layerNumber < mNumberOfLayers; layerNumber++) { + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { std::string directionName = std::to_string(direction); std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); mLayerName[direction][layerNumber] = layerName; @@ -272,7 +179,8 @@ void Detector::buildFT3V3b() LOG(info) << "Adding Layer " << layerName << " at z = " << z; // Add layers - auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); } } } @@ -288,10 +196,10 @@ void Detector::buildFT3NewVacuumVessel() LOG(info) << "Building FT3 Detector: After Upgrade Days March 2024 version"; - mNumberOfLayers = 9; - float sensorThickness = 30.e-4; - float layersx2X0 = 1.e-2; - std::vector> layersConfigCSide{ + const int numberOfLayers = 9; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfigCSide{ {77., 7.0, 35., layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} {100., 7.0, 35., layersx2X0}, {122., 7.0, 35., layersx2X0}, @@ -302,7 +210,7 @@ void Detector::buildFT3NewVacuumVessel() {300., 7.0, 68.f, layersx2X0}, {350., 7.0, 68.f, layersx2X0}}; - std::vector> layersConfigASide{ + const std::vector> layersConfigASide{ {77., 5.0, 35., layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} {100., 5.0, 35., layersx2X0}, {122., 5.0, 35., layersx2X0}, @@ -313,14 +221,11 @@ void Detector::buildFT3NewVacuumVessel() {300., 5.0, 68.f, layersx2X0}, {350., 5.0, 68.f, layersx2X0}}; - mLayerName.resize(2); - mLayerName[0].resize(mNumberOfLayers); - mLayerName[1].resize(mNumberOfLayers); - mLayerID.clear(); - mLayers.resize(2); + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); - for (auto direction : {0, 1}) { - for (int layerNumber = 0; layerNumber < mNumberOfLayers; layerNumber++) { + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { std::string directionName = std::to_string(direction); std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); mLayerName[direction][layerNumber] = layerName; @@ -339,7 +244,58 @@ void Detector::buildFT3NewVacuumVessel() LOG(info) << "Adding Layer " << layerName << " at z = " << z; // Add layers - auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); + } + } +} + +void Detector::buildFT3ScopingV3() +{ + // Build the FT3 detector according to v3 layout + // https://indico.cern.ch/event/1596309/contributions/6728167/attachments/3190117/5677220/2025-12-10-AW-ALICE3planning.pdf + // Middle disks inner radius 10 cm + // Outer disks inner radius 20 cm + + LOG(info) << "Building FT3 Detector: v3 scoping version"; + + const int numberOfLayers = 6; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + using LayerConfig = std::array; // {z_layer, r_in, r_out, Layerx2X0} + const std::array layersConfigCSide{LayerConfig{77., 10.0, 35., layersx2X0}, + LayerConfig{100., 10.0, 35., layersx2X0}, + LayerConfig{122., 10.0, 35., layersx2X0}, + LayerConfig{150., 20.0, 68.f, layersx2X0}, + LayerConfig{180., 20.0, 68.f, layersx2X0}, + LayerConfig{220., 20.0, 68.f, layersx2X0}}; + + const std::array layersConfigASide{LayerConfig{77., 10.0, 35., layersx2X0}, + LayerConfig{100., 10.0, 35., layersx2X0}, + LayerConfig{122., 10.0, 35., layersx2X0}, + LayerConfig{150., 20.0, 68.f, layersx2X0}, + LayerConfig{180., 20.0, 68.f, layersx2X0}, + LayerConfig{220., 20.0, 68.f, layersx2X0}}; + const std::array enabled{true, true, true, true, true, true}; // To enable or disable layers for debug purpose + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + mLayerName[direction].clear(); + const std::array& layerConfig = (direction == IdxBackwardDisks) ? layersConfigCSide : layersConfigASide; + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { + if (!enabled[layerNumber]) { + continue; + } + const std::string directionName = std::to_string(direction); + const std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); + mLayerName[direction].push_back(layerName.c_str()); + const float z = layerConfig[layerNumber][0]; + const float rIn = layerConfig[layerNumber][1]; + const float rOut = layerConfig[layerNumber][2]; + const float x0 = layerConfig[layerNumber][3]; + LOG(info) << "buildFT3ScopingV3 -> Adding Layer " << layerNumber << "/" << numberOfLayers << " " << layerName << " at z = " << z; + // Add layers + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); } } } @@ -351,10 +307,10 @@ void Detector::buildFT3Scoping() LOG(info) << "Building FT3 Detector: Scoping document version"; - mNumberOfLayers = 12; - float sensorThickness = 30.e-4; - float layersx2X0 = 1.e-2; - std::vector> layersConfig{ + const int numberOfLayers = 12; + const float sensorThickness = 30.e-4; + const float layersx2X0 = 1.e-2; + const std::vector> layersConfig{ {26., .5, 2.5, 0.1f * layersx2X0}, // {z_layer, r_in, r_out, Layerx2X0} {30., .5, 2.5, 0.1f * layersx2X0}, {34., .5, 2.5, 0.1f * layersx2X0}, @@ -368,26 +324,23 @@ void Detector::buildFT3Scoping() {300., 5.0, 68.f, layersx2X0}, {350., 5.0, 68.f, layersx2X0}}; - mLayerName.resize(2); - mLayerName[0].resize(mNumberOfLayers); - mLayerName[1].resize(mNumberOfLayers); - mLayerID.clear(); - mLayers.resize(2); + mLayerName[IdxBackwardDisks].resize(numberOfLayers); + mLayerName[IdxForwardDisks].resize(numberOfLayers); - for (auto direction : {0, 1}) { - for (int layerNumber = 0; layerNumber < mNumberOfLayers; layerNumber++) { + for (auto direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int layerNumber = 0; layerNumber < numberOfLayers; layerNumber++) { std::string directionName = std::to_string(direction); std::string layerName = GeometryTGeo::getFT3LayerPattern() + directionName + std::string("_") + std::to_string(layerNumber); mLayerName[direction][layerNumber] = layerName; auto& z = layersConfig[layerNumber][0]; - auto& rIn = layersConfig[layerNumber][1]; auto& rOut = layersConfig[layerNumber][2]; auto& x0 = layersConfig[layerNumber][3]; LOG(info) << "Adding Layer " << layerName << " at z = " << z; // Add layers - auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0); + const bool isMiddleLayer = layerNumber < 3; + auto& thisLayer = mLayers[direction].emplace_back(direction, layerNumber, layerName, z, rIn, rOut, x0, isMiddleLayer); } } } @@ -398,28 +351,7 @@ Detector::Detector(bool active) mTrackData(), mHits(o2::utils::createSimVector()) { - - // FT3 Base configuration parameters - auto& ft3BaseParam = FT3BaseParam::Instance(); - - if (ft3BaseParam.configFile != "") { - LOG(info) << "FT3 Geometry configuration file provided. Overriding FT3Base.geoModel configuration."; - buildFT3FromFile(ft3BaseParam.configFile); - - } else { - switch (ft3BaseParam.geoModel) { - case Default: - buildFT3NewVacuumVessel(); // FT3 after Upgrade days March 2024 - break; - case Telescope: - buildBasicFT3(ft3BaseParam); // BasicFT3 = Parametrized telescopic detector (equidistant layers) - break; - default: - LOG(fatal) << "Invalid Geometry.\n"; - break; - } - } - exportLayout(); + buildFT3ScopingV3(); // v3 Dec 25 } //_________________________________________________________________________________________________ @@ -430,9 +362,8 @@ Detector::Detector(const Detector& rhs) /// Container for data points mHits(o2::utils::createSimVector()) { - mLayerID = rhs.mLayerID; mLayerName = rhs.mLayerName; - mNumberOfLayers = rhs.mNumberOfLayers; + mActiveSensorMap = rhs.mActiveSensorMap; } //_________________________________________________________________________________________________ @@ -463,9 +394,8 @@ Detector& Detector::operator=(const Detector& rhs) // base class assignment base::Detector::operator=(rhs); - mLayerID = rhs.mLayerID; mLayerName = rhs.mLayerName; - mNumberOfLayers = rhs.mNumberOfLayers; + mActiveSensorMap = rhs.mActiveSensorMap; mLayers = rhs.mLayers; mTrackData = rhs.mTrackData; @@ -481,8 +411,6 @@ void Detector::InitializeO2Detector() // Define the list of sensitive volumes LOG(info) << "Initialize FT3 O2Detector"; - mGeometryTGeo = GeometryTGeo::Instance(); - defineSensitiveVolumes(); } @@ -494,11 +422,15 @@ bool Detector::ProcessHits(FairVolume* vol) return kFALSE; } - int lay = 0, volID = vol->getMCid(); - while ((lay <= mLayerID.size()) && (volID != mLayerID[lay])) { - ++lay; + int volID = vol->getMCid(); + + auto it = mActiveSensorMap.find(volID); + if (it == mActiveSensorMap.end()) { + return kFALSE; // Not a sensitive volume } + int lay = it->second; + auto stack = (o2::data::Stack*)fMC->GetStack(); bool startHit = false, stopHit = false; @@ -631,12 +563,10 @@ void Detector::ConstructGeometry() void Detector::createGeometry() { - mGeometryTGeo = GeometryTGeo::Instance(); - TGeoVolume* volFT3 = new TGeoVolumeAssembly(GeometryTGeo::getFT3VolPattern()); TGeoVolume* volIFT3 = new TGeoVolumeAssembly(GeometryTGeo::getFT3InnerVolPattern()); - LOG(info) << "GeometryBuilder::buildGeometry volume name = " << GeometryTGeo::getFT3VolPattern(); + LOG(info) << "FT3: createGeometry volume name = " << GeometryTGeo::getFT3VolPattern(); TGeoVolume* vALIC = gGeoManager->GetVolume("barrel"); if (!vALIC) { @@ -648,70 +578,31 @@ void Detector::createGeometry() LOG(info) << "Running simulation with no beam pipe."; } - LOG(debug) << "FT3 createGeometry: " - << Form("gGeoManager name is %s title is %s", gGeoManager->GetName(), gGeoManager->GetTitle()); - - if (mLayers.size() == 2) { // V1 and telescope - if (!A3IPvac) { - for (int direction : {0, 1}) { // Backward layers at mLayers[0]; Forward layers at mLayers[1] - std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << "Creating FT3 " << directionString << " layers:"; - for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { - mLayers[direction][iLayer].createLayer(volFT3); - } - } - vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); - } else { // If beampipe is enabled append inner disks to beampipe filling volume, this should be temporary. - for (int direction : {0, 1}) { - std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << "Creating FT3 " << directionString << " layers:"; - for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { - if (iLayer < 3) { - mLayers[direction][iLayer].createLayer(volIFT3); - } else { - mLayers[direction][iLayer].createLayer(volFT3); - } - } - } - A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); - vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); - } - - for (auto direction : {0, 1}) { - std::string directionString = direction ? "Forward" : "Backward"; - LOG(info) << "Registering FT3 " << directionString << " LayerIDs:"; + // This will need to adapt to the new scheme + if (!A3IPvac) { + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { // Backward layers at mLayers[0]; Forward layers at mLayers[1] + const std::string directionString = direction ? "Forward" : "Backward"; + LOG(info) << " Creating FT3 without beampipe " << directionString << " layers:"; for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { - auto layerID = gMC ? TVirtualMC::GetMC()->VolId(Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer)) : 0; - mLayerID.push_back(layerID); - LOG(info) << " " << directionString << " layer " << iLayer << " LayerID " << layerID; + mLayers[direction][iLayer].createLayer(volFT3); } } - } - - if (mLayers.size() == 1) { // All layers registered at mLayers[0], used when building from file - LOG(info) << "Creating FT3 layers:"; - if (A3IPvac) { - for (int iLayer = 0; iLayer < mLayers[0].size(); iLayer++) { - if (std::abs(mLayers[0][iLayer].getZ()) < 25) { - mLayers[0][iLayer].createLayer(volIFT3); + vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); + } else { // If beampipe is enabled append inner disks to beampipe filling volume, this should be temporary. + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + const std::string directionString = direction ? "Forward" : "Backward"; + LOG(info) << " Creating FT3 " << directionString << " layers:"; + for (int iLayer = 0; iLayer < mLayers[direction].size(); iLayer++) { + LOG(info) << " Creating " << directionString << " layer " << iLayer; + if (mLayers[direction][iLayer].getIsInMiddleLayer()) { // ML disks + mLayers[direction][iLayer].createLayer(volIFT3); } else { - mLayers[0][iLayer].createLayer(volFT3); + mLayers[direction][iLayer].createLayer(volFT3); } } - A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); - vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); - } else { - for (int iLayer = 0; iLayer < mLayers[0].size(); iLayer++) { - mLayers[0][iLayer].createLayer(volFT3); - } - vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); - } - LOG(info) << "Registering FT3 LayerIDs:"; - for (int iLayer = 0; iLayer < mLayers[0].size(); iLayer++) { - auto layerID = gMC ? TVirtualMC::GetMC()->VolId(Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), 0, iLayer)) : 0; - mLayerID.push_back(layerID); - LOG(info) << " mLayerID[" << iLayer << "] = " << layerID; } + A3IPvac->AddNode(volIFT3, 2, new TGeoTranslation(0., 0., 0.)); + vALIC->AddNode(volFT3, 2, new TGeoTranslation(0., 30., 0.)); } } @@ -719,29 +610,63 @@ void Detector::createGeometry() void Detector::defineSensitiveVolumes() { TGeoManager* geoManager = gGeoManager; - TGeoVolume* v; - - TString volumeName; - LOG(info) << "Adding FT3 Sensitive Volumes"; - - // The names of the FT3 sensitive volumes have the format: FT3Sensor_(0,1)_(0...sNumberLayers-1) - if (mLayers.size() == 2) { - for (int direction : {0, 1}) { - for (int iLayer = 0; iLayer < mNumberOfLayers; iLayer++) { - volumeName = o2::ft3::GeometryTGeo::getFT3SensorPattern() + std::to_string(iLayer); - v = geoManager->GetVolume(Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer)); - LOG(info) << "Adding FT3 Sensitive Volume => " << v->GetName(); - AddSensitiveVolume(v); + + // Get the flat list of ALL volumes present in the geometry + TObjArray* allVolumes = geoManager->GetListOfVolumes(); + int nVolumes = allVolumes->GetEntriesFast(); + + LOG(info) << "Adding FT3 Sensitive Volumes by iterating over all geometry volumes..."; + + for (int direction : {IdxBackwardDisks, IdxForwardDisks}) { + for (int iLayer = 0; iLayer < getNumberOfLayers(); iLayer++) { + int iSens = 0; + + // Build the "signatures" (prefixes) of the names for the various layouts for this specific layer and direction: + + // 1. Trapezoidal/Cylindrical (format: FT3Sensor__) + std::string sig1 = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), direction, iLayer); + + // 2. Segmented front/back (format: FT3Sensor_front___...) + std::string sig2 = "FT3Sensor_front_" + std::to_string(iLayer) + "_" + std::to_string(direction); + std::string sig3 = "FT3Sensor_back_" + std::to_string(iLayer) + "_" + std::to_string(direction); + + // 3. SegmentedStave (format: FT3Sensor___...) + // Add the trailing underscore to avoid confusing it with sig1 + std::string sig4 = "FT3Sensor_" + std::to_string(iLayer) + "_" + std::to_string(direction) + "_"; + + // Iterate over all existing volumes to find matches + for (int i = 0; i < nVolumes; ++i) { + TGeoVolume* v = (TGeoVolume*)allVolumes->At(i); + std::string vName = v->GetName(); + + // Explicitly exclude the inactive silicon regions created in FT3Module + if (vName.find("Inactive") != std::string::npos || vName.find("inactive") != std::string::npos) { + continue; + } + + // Check if the volume name matches one of our active sensors + bool isMatch = false; + if (vName == sig1) { + isMatch = true; // Exact match for Trapezoidal/Cylindrical layouts + } else if (vName.find(sig2) == 0 || vName.find(sig3) == 0 || vName.find(sig4) == 0) { + isMatch = true; // Prefix match for Segmented and SegmentedStave layouts + } + + if (isMatch) { + AddSensitiveVolume(v); + int volID = gMC ? TVirtualMC::GetMC()->VolId(vName.c_str()) : 0; + if (volID > 0) { + mActiveSensorMap[volID] = iLayer; + } + iSens++; + } } - } - } - if (mLayers.size() == 1) { - for (int iLayer = 0; iLayer < mLayers[0].size(); iLayer++) { - volumeName = o2::ft3::GeometryTGeo::getFT3SensorPattern() + std::to_string(iLayer); - v = geoManager->GetVolume(Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mLayers[0][iLayer].getDirection(), iLayer)); - LOG(info) << "Adding FT3 Sensitive Volume => " << v->GetName(); - AddSensitiveVolume(v); + if (iSens == 0) { + LOG(error) << "NO sensitive volume found for direction " << direction << ", layer " << iLayer; + } else { + LOG(info) << iSens << " sensitive volume(s) added for direction " << direction << " layer " << iLayer; + } } } } diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx index 01b512b996af2..d8245fa1d34b4 100644 --- a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Layer.cxx @@ -16,20 +16,18 @@ #include "FT3Simulation/FT3Layer.h" #include "FT3Base/GeometryTGeo.h" -#include "FT3Simulation/Detector.h" - -#include // for LOG +#include "FT3Base/FT3BaseParam.h" +#include "FT3Simulation/FT3ModuleConstants.h" #include // for TGeoManager, gGeoManager #include // for TGeoCombiTrans, TGeoRotation, etc #include // for TGeoTube, TGeoTubeSeg +#include // for TGeoTrap #include // for TGeoVolume, TGeoVolumeAssembly #include // for TGeoCompositeShape #include "TMathBase.h" // for Abs #include // for Sin, RadToDeg, DegToRad, Cos, Tan, etc -#include // for snprintf - class TGeoMedium; using namespace TMath; @@ -40,29 +38,342 @@ ClassImp(FT3Layer); FT3Layer::~FT3Layer() = default; -FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerName, Float_t z, Float_t rIn, Float_t rOut, Float_t Layerx2X0) +TGeoMaterial* FT3Layer::carbonFiberMat = nullptr; +TGeoMedium* FT3Layer::medCarbonFiber = nullptr; + +TGeoMaterial* FT3Layer::kaptonMat = nullptr; +TGeoMedium* FT3Layer::kaptonMed = nullptr; + +TGeoMaterial* FT3Layer::waterMat = nullptr; +TGeoMedium* FT3Layer::waterMed = nullptr; + +TGeoMaterial* FT3Layer::foamMat = nullptr; +TGeoMedium* FT3Layer::medFoam = nullptr; + +FT3Layer::FT3Layer(Int_t layerDirection, Int_t layerNumber, std::string layerName, Float_t z, Float_t rIn, Float_t rOut, Float_t Layerx2X0, bool partOfMiddleLayers) { // Creates a simple parametrized EndCap layer covering the given // pseudorapidity range at the z layer position mDirection = layerDirection; mLayerNumber = layerNumber; + mIsMiddleLayer = partOfMiddleLayers; mLayerName = layerName; mZ = layerDirection ? std::abs(z) : -std::abs(z); mx2X0 = Layerx2X0; mInnerRadius = rIn; mOuterRadius = rOut; - auto Si_X0 = 9.5; + const double Si_X0 = 9.5; mChipThickness = Layerx2X0 * Si_X0; + mSensorThickness = 0.005; // assume 50 microns of active thickness (for sensor volumes for trapezoidal disks) + + // Sanity checks + if (std::isnan(mZ)) { + LOG(fatal) << "FT3 Layer " << mLayerNumber << " has z = NaN, which is not a valid number."; + } + if (mZ < 0.001 && mZ > -0.001) { + LOG(fatal) << "FT3 Layer " << mLayerNumber << " has z = " << mZ << " cm, which is very close to 0."; + } LOG(info) << "Creating FT3 Layer " << mLayerNumber << " ; direction " << mDirection; LOG(info) << " Using silicon X0 = " << Si_X0 << " to emulate layer radiation length."; LOG(info) << " Layer z = " << mZ << " ; R_in = " << mInnerRadius << " ; R_out = " << mOuterRadius << " ; x2X0 = " << mx2X0 << " ; ChipThickness = " << mChipThickness; } +void FT3Layer::initialize_mat() +{ + + if (carbonFiberMat) { + return; + } + + carbonFiberMat = new TGeoMaterial("CarbonFiber", 12.0, 6.0, 1.6); + medCarbonFiber = new TGeoMedium("CarbonFiber", 1, carbonFiberMat); + + auto* itsC = new TGeoElement("FT3_C", "Carbon", 6, 12.0107); + + auto* itsFoam = new TGeoMixture("FT3_Foam", 1); + itsFoam->AddElement(itsC, 1); + itsFoam->SetDensity(0.17); + + medFoam = new TGeoMedium("FT3_Foam", 1, itsFoam); + foamMat = medFoam->GetMaterial(); + + kaptonMat = new TGeoMaterial("Kapton (cooling pipe)", 13.84, 6.88, 1.346); + kaptonMed = new TGeoMedium("Kapton (cooling pipe)", 1, kaptonMat); + + waterMat = new TGeoMaterial("Water", 18.01528, 8.0, 1.064); + waterMed = new TGeoMedium("Water", 2, waterMat); +} + +static double y_circle(double x, double radius) +{ + return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; +} + +void FT3Layer::createSeparationLayer_waterCooling(TGeoVolume* motherVolume, const std::string& separationLayerName) +{ + + FT3Layer::initialize_mat(); + + const double carbonFiberThickness = 0.01; // cm + const double foamSpacingThickness = 0.5; // cm + + TGeoTube* carbonFiberLayer = new TGeoTube(mInnerRadius, mOuterRadius, carbonFiberThickness / 2); + + // volumes + TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, medCarbonFiber); + TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, medCarbonFiber); + + carbonFiberLayerVol1->SetLineColor(kGray + 2); + carbonFiberLayerVol2->SetLineColor(kGray + 2); + + const double zSeparation = foamSpacingThickness / 2.0 + carbonFiberThickness / 2.0; + + motherVolume->AddNode(carbonFiberLayerVol1, 1, new TGeoTranslation(0, 0, mZ - zSeparation)); + motherVolume->AddNode(carbonFiberLayerVol2, 1, new TGeoTranslation(0, 0, mZ + zSeparation)); + + const double pipeOuterRadius = 0.20; + const double kaptonThickness = 0.0025; + const double pipeInnerRadius = pipeOuterRadius - kaptonThickness; + const double pipeMaxLength = mOuterRadius * 2.0; + + int name_it = 0; + + // positions of the pipes depending on the overlap of the sensors inactive regions: (ALICE 3 dimensions) + // partial: + // std::vector X_pos = {-63.2, -58.4, -53.6, -48.8, -44.0, -39.199999999999996, -34.4, -29.599999999999994, -24.799999999999997, -19.999999999999993, -15.199999999999998, -10.399999999999993, -5.599999999999998, -0.7999999999999936, 4.000000000000002, 8.800000000000006, 13.600000000000001, 18.400000000000006, 23.200000000000003, 28.000000000000007, 32.800000000000004, 37.60000000000001, 42.400000000000006, 47.20000000000001, 52.00000000000001, 56.80000000000001, 61.60000000000001, 66.4}; + // complete: + // std::vector X_pos = {-63.4, -58.8, -54.199999999999996, -49.599999999999994, -44.99999999999999, -40.39999999999999, -35.79999999999999, -31.199999999999992, -26.59999999999999, -21.999999999999993, -17.39999999999999, -12.799999999999994, -8.199999999999992, -3.5999999999999934, 1.000000000000008, 5.600000000000007, 10.200000000000008, 14.800000000000008, 19.40000000000001, 24.000000000000007, 28.60000000000001, 33.20000000000001, 37.80000000000001, 42.40000000000001, 47.000000000000014, 51.600000000000016, 56.20000000000002, 60.80000000000002, 65.40000000000002}; + std::vector X_pos = {-62.3168, -57.9836, -53.650400000000005, -49.317200000000014, -44.984000000000016, -40.65080000000002, -36.31760000000002, -31.984400000000026, -27.65120000000003, -23.318000000000037, -18.98480000000004, -14.651600000000043, -10.318400000000047, -5.98520000000005, -1.6520000000000519, 2.6811999999999445, 7.014399999999941, 11.347599999999936, 15.680799999999934, 20.01399999999993, 24.347199999999926, 28.68039999999992, 33.013599999999926, 37.34679999999992, 41.980000000000004, 46.613200000000006, 51.246399999999994, 55.87960000000001, 60.5128}; + + for (double xPos : X_pos) { + + double pipeLength = pipeMaxLength; + double yMax = 0.0; + + TGeoRotation* rotation = new TGeoRotation(); + rotation->RotateX(90); + + if (std::abs(xPos) < mInnerRadius) { + double yInner = std::abs(y_circle(xPos, mInnerRadius)); + double yOuter = std::abs(y_circle(xPos, mOuterRadius)); + + yMax = 2 * yOuter; + pipeLength = yMax; + + double positiveYLength = yOuter - yInner; + + TGeoVolume* kaptonPipePos = new TGeoVolume((separationLayerName + "_KaptonPipePos_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), kaptonMed); + kaptonPipePos->SetLineColor(kGray); + TGeoVolume* waterVolumePos = new TGeoVolume((separationLayerName + "_WaterVolumePos_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), waterMed); + waterVolumePos->SetLineColor(kBlue); + + motherVolume->AddNode(waterVolumePos, 1, new TGeoCombiTrans(xPos, (yInner + yOuter) / 2.0, mZ, rotation)); + + TGeoVolume* kaptonPipeNeg = new TGeoVolume((separationLayerName + "_KaptonPipeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, positiveYLength / 2), kaptonMed); + kaptonPipeNeg->SetLineColor(kGray); + TGeoVolume* waterVolumeNeg = new TGeoVolume((separationLayerName + "_WaterVolumeNeg_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, positiveYLength / 2), waterMed); + waterVolumeNeg->SetLineColor(kBlue); + + motherVolume->AddNode(waterVolumeNeg, 1, new TGeoCombiTrans(xPos, -(yInner + yOuter) / 2.0, mZ, rotation)); + + motherVolume->AddNode(kaptonPipePos, 1, new TGeoCombiTrans(xPos, (yInner + yOuter) / 2.0, mZ, rotation)); + motherVolume->AddNode(kaptonPipeNeg, 1, new TGeoCombiTrans(xPos, -(yInner + yOuter) / 2.0, mZ, rotation)); + + } else { + + double yOuter = std::abs(y_circle(xPos, mOuterRadius)); + yMax = 2 * yOuter; + pipeLength = yMax; + + TGeoVolume* kaptonPipe = new TGeoVolume((separationLayerName + "_KaptonPipe_" + std::to_string(name_it)).c_str(), new TGeoTube(pipeInnerRadius, pipeOuterRadius, pipeLength / 2), kaptonMed); + kaptonPipe->SetLineColor(kGray); + TGeoVolume* waterVolume = new TGeoVolume((separationLayerName + "_WaterVolume_" + std::to_string(name_it)).c_str(), new TGeoTube(0.0, pipeInnerRadius, pipeLength / 2), waterMed); + waterVolume->SetLineColor(kBlue); + + motherVolume->AddNode(waterVolume, 1, new TGeoCombiTrans(xPos, 0, mZ, rotation)); + motherVolume->AddNode(kaptonPipe, 1, new TGeoCombiTrans(xPos, 0, mZ, rotation)); + } + + name_it++; + } +} + +void FT3Layer::createSeparationLayer(TGeoVolume* motherVolume, const std::string& separationLayerName) +{ + + FT3Layer::initialize_mat(); + + constexpr double carbonFiberThickness = 0.01; // cm + constexpr double foamSpacingThickness = 1.0; // cm + + TGeoTube* carbonFiberLayer = new TGeoTube(mInnerRadius, mOuterRadius, carbonFiberThickness / 2); + TGeoTube* foamLayer = new TGeoTube(mInnerRadius, mOuterRadius, foamSpacingThickness / 2); + + // volumes + TGeoVolume* carbonFiberLayerVol1 = new TGeoVolume((separationLayerName + "_CarbonFiber1").c_str(), carbonFiberLayer, medCarbonFiber); + TGeoVolume* foamLayerVol = new TGeoVolume((separationLayerName + "_Foam").c_str(), foamLayer, medFoam); + TGeoVolume* carbonFiberLayerVol2 = new TGeoVolume((separationLayerName + "_CarbonFiber2").c_str(), carbonFiberLayer, medCarbonFiber); + + carbonFiberLayerVol1->SetLineColor(kGray + 2); + foamLayerVol->SetLineColor(kBlack); + foamLayerVol->SetFillColorAlpha(kBlack, 1.0); + carbonFiberLayerVol2->SetLineColor(kGray + 2); + + const double zSeparation = foamSpacingThickness / 2.0 + carbonFiberThickness / 2.0; + + motherVolume->AddNode(carbonFiberLayerVol1, 1, new TGeoTranslation(0, 0, 0 - zSeparation)); + motherVolume->AddNode(foamLayerVol, 1, new TGeoTranslation(0, 0, 0)); + motherVolume->AddNode(carbonFiberLayerVol2, 1, new TGeoTranslation(0, 0, 0 + zSeparation)); +} + +void FT3Layer::createReferenceCircles(TGeoVolume* motherVolume, const std::string& name) +{ + + // create reference circles at the inner and outer radius of the layer, for visualization purposes + TGeoTube* innerCircle = new TGeoTube(mInnerRadius - 0.1, mInnerRadius + 0.1, 0.01); + TGeoTube* outerCircle = new TGeoTube(mOuterRadius - 0.1, mOuterRadius + 0.1, 0.01); + + TGeoVolume* innerCircleVol = new TGeoVolume((mLayerName + "_InnerCircle").c_str(), innerCircle, gGeoManager->GetMedium("FT3_AIR$")); + TGeoVolume* outerCircleVol = new TGeoVolume((mLayerName + "_OuterCircle").c_str(), outerCircle, gGeoManager->GetMedium("FT3_AIR$")); + + innerCircleVol->SetLineColor(kRed); + outerCircleVol->SetLineColor(kBlue); + + double z_position = mDirection ? 0.5 : -0.5; + + motherVolume->AddNode(innerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); + motherVolume->AddNode(outerCircleVol, 1, new TGeoTranslation(0, 0, z_position)); +} + void FT3Layer::createLayer(TGeoVolume* motherVolume) { - if (mLayerNumber >= 0) { - // Create tube, set sensitive volume, add to mother volume + auto& ft3Params = FT3BaseParam::Instance(); + + if (mLayerNumber < 0) { + LOG(fatal) << "Invalid layer number " << mLayerNumber << " for FT3 layer."; + } + + LOG(info) << "FT3: ft3Params.layoutFT3 = " << ft3Params.layoutFT3 + << " Creating Layer " << mLayerNumber << " at z=" << mZ + << " with direction " << mDirection; + + // ### options for ML and OT disk layout + if (ft3Params.layoutFT3 == kTrapezoidal /*|| (mIsMiddleLayer && ft3Params.layoutFT3 == kSegmented)*/) { + // trapezoidal ML+OT disks + // (disks with TGeoTubes doesn'n work properly in ACTS, due to polar coordinates on TGeoTube sides) + + // (!) Currently (March 12, 2026), only OT disks are segmented --> use Trapezoidal option for ML disks as a simplified segmentation + // To be changed to "true" paving with modules, as for the OT disks + + std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber); + std::string sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); + std::string passiveName = o2::ft3::GeometryTGeo::getFT3PassivePattern() + std::to_string(mLayerNumber); + + TGeoMedium* medSi = gGeoManager->GetMedium("FT3_SILICON$"); + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mChipThickness / 2); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kGray); + + const int NtrapezoidalSegments = ft3Params.nTrapezoidalSegments; + + const double dz = mChipThickness / 2; + const double dzSensor = mSensorThickness / 2; + + const double dphi = 2.0 * TMath::Pi() / NtrapezoidalSegments; + double innerRadiusTrapezoidCorner = mInnerRadius / sin((TMath::Pi() - dphi) / 2); // to ensure that the trapezoid segments do not extend beyond the volume + + const double rc = 0.5 * (innerRadiusTrapezoidCorner + mOuterRadius) * TMath::Cos(0.5 * dphi); // radius of tile center + const double h = 0.5 * (mOuterRadius - innerRadiusTrapezoidCorner) * TMath::Cos(0.5 * dphi); // half radial length + + // chord lengths at inner/outer radii + const double bl = innerRadiusTrapezoidCorner * TMath::Sin(0.5 * dphi); // half lower base + const double tl = mOuterRadius * TMath::Sin(0.5 * dphi); // half upper base + + // create trapezoids + for (int iTr = 0; iTr < NtrapezoidalSegments; ++iTr) { + // chip volume + auto trdShapeChip = new TGeoTrap(dz, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidChipVolume = new TGeoVolume(chipName.c_str(), trdShapeChip, medSi); + trapezoidChipVolume->SetLineColor(kCyan); + trapezoidChipVolume->SetTransparency(50); + + // sensor volume + auto trdShapeSensor = new TGeoTrap(dzSensor, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidSensorVolume = new TGeoVolume(sensName.c_str(), trdShapeSensor, medSi); + trapezoidSensorVolume->SetLineColor(kYellow); + + // placing sensor in chip: + const double zSensorInChip = (dz - dzSensor) * (mZ < 0 ? 1 : -1); // place sensor at the outer face of the chip, towards the incoming particles + TGeoCombiTrans* transSens = new TGeoCombiTrans(); + transSens->SetTranslation(0, 0, zSensorInChip); + trapezoidChipVolume->AddNode(trapezoidSensorVolume, iTr, transSens); + + // passive volume + auto trdShapePassive = new TGeoTrap(dz - dzSensor, + 0.0, 0.0, // theta, phi + h, // h1 + bl, // bl1 + tl, // tl1 + 0.0, // alpha1 + h, // h2 + bl, // bl2 + tl, // tl2 + 0.0); // alpha2 + TGeoVolume* trapezoidPassiveVolume = new TGeoVolume(passiveName.c_str(), trdShapePassive, medSi); + trapezoidPassiveVolume->SetLineColor(kGray); + + // placing passive volume in chip: + const double zPassiveInChip = (-dzSensor) * (mZ < 0 ? 1 : -1); // place passive volume at the outer face of the chip, towards the incoming particles + TGeoCombiTrans* transPassive = new TGeoCombiTrans(); + transPassive->SetTranslation(0, 0, zPassiveInChip); + trapezoidChipVolume->AddNode(trapezoidPassiveVolume, iTr, transPassive); + + // prepare placing of chip in layer: + const double phi_c = (iTr + 0.5) * dphi; // sector center + const double phi_deg = phi_c * 180.0 / TMath::Pi(); + + // center of tile + const double x = rc * TMath::Cos(phi_c); + const double y = rc * TMath::Sin(phi_c); + const double z = 0.0; + + // local +Y should point radially outward + auto rot = new TGeoRotation(); + rot->RotateZ(phi_deg - 90.0); + auto transf = new TGeoCombiTrans(x, y, z, rot); + + layerVol->AddNode(trapezoidChipVolume, iTr, transf); + } + + LOG(info) << "Inserting " << NtrapezoidalSegments << " trapezoidal segments (Rmin=" + << mInnerRadius << ", Rmax=" << mOuterRadius << ", z = " << mZ << "cm) inside " << layerVol->GetName(); + + auto* diskRotation = new TGeoRotation("TrapezoidalDiskRotation", 0, 0, 0); + auto* diskCombiTrans = new TGeoCombiTrans(0, 0, mZ, diskRotation); + motherVolume->AddNode(layerVol, 1, diskCombiTrans); + } else if (ft3Params.layoutFT3 == kCylindrical) { + // cylindrical ML+OT disks std::string chipName = o2::ft3::GeometryTGeo::getFT3ChipPattern() + std::to_string(mLayerNumber), sensName = Form("%s_%d_%d", GeometryTGeo::getFT3SensorPattern(), mDirection, mLayerNumber); @@ -92,7 +403,83 @@ void FT3Layer::createLayer(TGeoVolume* motherVolume) LOG(info) << "Inserting " << layerVol->GetName() << " inside " << motherVolume->GetName(); motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else if (ft3Params.layoutFT3 == kSegmented || + (ft3Params.layoutFT3 == kSegmentedStaveOTOnly && mIsMiddleLayer)) { + FT3Module module; - return; + // layer structure + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); + + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoVolume* layerVol = nullptr; + // Add a little additional room in radius + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.1, mOuterRadius + 0.1, 1.5); + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow + 2); + // createSeparationLayer_waterCooling(motherVolume, separationLayerName); + createSeparationLayer(layerVol, separationLayerName); + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "front", "rectangular", layerVol); + module.createModule(0, mLayerNumber, mDirection, mInnerRadius, mOuterRadius, 0., "back", "rectangular", layerVol); + + // Finally put everything in the mother volume + auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); + // need to shift outwards always, so + forwards and - backwards + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + 0, FwdDiskRotation); + + LOG(info) << "Inserting " << layerVol->GetName() << " (Rmin=" << mInnerRadius << ", Rmax=" << mOuterRadius << ", z=" << mZ << "cm) inside " << motherVolume->GetName(); + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else if (ft3Params.layoutFT3 == kSegmentedStave || + ft3Params.layoutFT3 == kSegmentedStaveOTOnly) { + FT3Module module; + + // layer structure + std::string frontLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Front"; + std::string backLayerName = o2::ft3::GeometryTGeo::getFT3LayerPattern() + std::to_string(mDirection) + std::to_string(mLayerNumber) + "_Back"; + std::string separationLayerName = "FT3SeparationLayer" + std::to_string(mDirection) + std::to_string(mLayerNumber); + + TGeoMedium* medAir = gGeoManager->GetMedium("FT3_AIR$"); + TGeoVolume* layerVol = nullptr; + + // set up stave config, differs between ML and OT disks + const Constants::StaveConfig& staveConfig = Constants::getStaveConfig(mIsMiddleLayer); + + // need a thicker air layer to encompass the staves (4.5cm high, 1.2cm offsets) + // stave face is at z=0 (or +-z_offset_stave), meaning that volumes are at + // ~-+1cm < z < ~+-6cm, the +- referring forward/backward discs + double z_layer_thickness = // need to shift internally with this + o2::ft3::ModuleConstants::staveTriangleHeight + + o2::ft3::ModuleConstants::z_offsetStave(staveConfig.x_midpoint_spacing) + + o2::ft3::ModuleConstants::siliconThickness + + o2::ft3::ModuleConstants::copperThickness + + o2::ft3::ModuleConstants::kaptonThickness + + o2::ft3::ModuleConstants::epoxyThickness * 2 + + 0.5; // add some extra room to ensure all volumes are encapsulated + + // shift stave volumes into layer volume, since nominal z_{stave face} = 0 + double z_local_offset = z_layer_thickness / 2.0; + TGeoTube* layer = new TGeoTube(mInnerRadius - 0.2, mOuterRadius + 2.5, z_layer_thickness / 2); // margins to ensure staves are fully encapsulated in the layer volume + layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + + if (ft3Params.drawReferenceCircles) { + std::string referenceCirclesName = "ReferenceCircles_Dir" + std::to_string(mDirection) + "_Layer" + std::to_string(mLayerNumber); + createReferenceCircles(layerVol, referenceCirclesName); // for visualization purposes + } + + // need the -0.5 added to local offset to ensure all sensor modules are inside the layer + module.createModule_staveGeo(0., mLayerNumber, mDirection, mInnerRadius, + mOuterRadius, z_local_offset, staveConfig, layerVol); + // Finally put everything in the mother volume + auto* FwdDiskRotation = new TGeoRotation("FwdDiskRotation", 0, 0, 180); + // need to shift outwards always, so + forwards and - backwards + double z_offset_directional = mDirection ? z_local_offset : -z_local_offset; + auto* FwdDiskCombiTrans = new TGeoCombiTrans(0, 0, mZ + z_offset_directional, FwdDiskRotation); + + LOG(info) << "Inserting " << layerVol->GetName() << " (Rmin=" << mInnerRadius << ", Rmax=" << mOuterRadius << ", z=" << mZ << "cm, segmented disk with staves) inside " << motherVolume->GetName(); + + motherVolume->AddNode(layerVol, 1, FwdDiskCombiTrans); + } else { + LOG(fatal) << "Unknown FT3 layout option: " << static_cast(ft3Params.layoutFT3); } } diff --git a/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx new file mode 100644 index 0000000000000..c439da9d539d0 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/FT3/simulation/src/FT3Module.cxx @@ -0,0 +1,1405 @@ +// 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 FT3Module.cxx +/// \brief Implementation of the FT3Module class + +#include "FT3Simulation/FT3Module.h" +#include "FT3Base/FT3BaseParam.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TGeoMaterial* FT3Module::siliconMat = nullptr; +TGeoMedium* FT3Module::siliconMed = nullptr; + +TGeoMaterial* FT3Module::copperMat = nullptr; +TGeoMedium* FT3Module::copperMed = nullptr; + +TGeoMaterial* FT3Module::kaptonMat = nullptr; +TGeoMedium* FT3Module::kaptonMed = nullptr; + +TGeoMaterial* FT3Module::epoxyMat = nullptr; +TGeoMedium* FT3Module::epoxyMed = nullptr; + +TGeoMaterial* FT3Module::AluminumMat = nullptr; +TGeoMedium* FT3Module::AluminumMed = nullptr; + +TGeoMaterial* FT3Module::carbonFiberMat = nullptr; +TGeoMedium* FT3Module::carbonFiberMed = nullptr; + +void FT3Module::initialize_materials() +{ + LOG(debug) << "FT3Module: initialize_materials"; + if (siliconMat) { + return; + } + + TGeoManager* geoManager = gGeoManager; + + auto* itsH = new TGeoElement("FT3_H", "Hydrogen", 1, 1.00794); + auto* itsC = new TGeoElement("FT3_C", "Carbon", 6, 12.0107); + auto* itsO = new TGeoElement("FT3_O", "Oxygen", 8, 15.994); + + siliconMat = new TGeoMaterial("FT3_Silicon", 28.0855, 14, 2.33); + siliconMed = new TGeoMedium("FT3_Silicon", 1, siliconMat); + + copperMat = new TGeoMaterial("FT3_Copper", 63.546, 29, 8.96); + copperMed = new TGeoMedium("FT3_Copper", 2, copperMat); + + kaptonMat = new TGeoMaterial("FT3_Kapton", 13.84, 6.88, 1.346); + kaptonMed = new TGeoMedium("FT3_Kapton", 3, kaptonMat); + + // TODO: Check with Rene the exact type of carbon fiber + carbonFiberMat = new TGeoMaterial("FT3_Carbon", 12.0107, 6, 1.8); + carbonFiberMed = new TGeoMedium("FT3_Carbon", 6, carbonFiberMat); + + // Epoxy: C18 H19 O3 + auto* itsEpoxy = new TGeoMixture("FT3_Epoxy", 3); + itsEpoxy->AddElement(itsC, 18); + itsEpoxy->AddElement(itsH, 19); + itsEpoxy->AddElement(itsO, 3); + itsEpoxy->SetDensity(2.186); + + epoxyMed = new TGeoMedium("FT3_Epoxy", 4, itsEpoxy); + epoxyMat = epoxyMed->GetMaterial(); + + AluminumMat = new TGeoMaterial("Aluminum", 26.98, 13, 2.7); + AluminumMed = new TGeoMedium("Aluminum", 5, AluminumMat); + LOG(debug) << "FT3Module: done initialize_materials"; +} + +double calculate_y_circle(double x, double radius) +{ + return (x * x < radius * radius) ? std::sqrt(radius * radius - x * x) : 0; +} + +std::pair calculate_y_range( + double x_left, double x_right, double Rin, double Rout) +{ + double max_y_abs; + double min_y_abs; + /* + * Have 5 cases: + * (1) Stave wholly on the left of inner radius + * (2) Stave wholly on the left, but within inner radius + * (3) Stave crosses the middle x=0 + * (4) Stave wholly on the right, but within inner radius + * (5) Stave wholly on the right of inner radius + */ + if (x_right < -Rin) { + // Stave is completely on the left of inner radius + min_y_abs = 0; + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < -Constants::sensor2x1_width) { + // Stave is completely on the left, but within inner radius + min_y_abs = calculate_y_circle(x_right, Rin); + max_y_abs = calculate_y_circle(x_left, Rout); + } else if (x_left < 0) { + // Stave crosses the middle x=0 + min_y_abs = Rin; + // x_right should be > 0, but might have FLP issues, so do abs nonetheless + max_y_abs = calculate_y_circle(std::max(std::abs(x_left), std::abs(x_right)), Rout); + } else if (x_left < Rin) { + // Stave is completely on the right, but within inner radius + min_y_abs = calculate_y_circle(x_left, Rin); + max_y_abs = calculate_y_circle(x_right, Rout); + } else { + // Stave is completely on the right of inner radius + min_y_abs = 0.; + max_y_abs = calculate_y_circle(x_right, Rout); + } + return {min_y_abs, max_y_abs}; +} + +/* + * This function is a helper function which will pad out the stave with sensors + * until there is no more space available. + * + * Arguments: + * y_positions: a pair of vectors, where each vector contains pairs of + * y position and stack height for the positive and negative y positions respectively. + * This argument will be appended with the new sensor positions and stack heights. + * Rout: the outer radius of the layer + * Rin: the inner radius of the layer + * x_left: the x position of the left edge of the sensor to be placed + * kSensorStack: the number of sensors to be stacked on top of each other + * tolerance: the tolerance to be subtracted from the maximum y position to avoid + * placing sensors too close to the edge. If this is negative, it effectively + * means that you can place sensors beyond the nominal disc edge + * y_start: the y positions to start placing sensors, + * for positive and negative y respectively + */ +void FT3Module::fill_stave(PosNegPositionTypes& y_positions, double Rin, double Rout, + double x_left, unsigned kSensorStack, PositionRangeType y_ranges, + std::pair& absAllowedYRange) +{ + // start with upper half of the stave, then mirror to the bottom half + // add the height of kSensorStack sensors + the gaps in between them + double sensorStackHeight = Constants::getStackHeight(kSensorStack); + double sensorAbsStackYShift = sensorStackHeight + Constants::stackGap; + + // in case a big tolerance is given, cut on the given range instead + double max_sensor_y_abs = std::min(absAllowedYRange.second, y_ranges.first.second); + + double y_top; // top half of the xy grid, y>0 + // either start at given value (adjusted for tolerance), or at last placed sensors + if (!y_positions.first.empty()) { // sensors already placed + double previousStackHeight = Constants::getStackHeight(y_positions.first.back().second); + y_top = y_positions.first.back().first + previousStackHeight + Constants::stackGap; + } else if (absAllowedYRange.first > 0) { + // there is a minimum inner value --> 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) +{ + + LOG(debug) << "FT3Module: create_layout - Layer " << layerNumber << ", Direction " << direction << ", Face " << face; + TGeoManager* geoManager = gGeoManager; + + FT3Module::initialize_materials(); + + // double sensor_width = 2.5; + // double sensor_height = 9.6; + // double active_width = 2.3; + // double active_height = 9.6; + + double sensor_width = 5.0; + double sensor_height = 9.6; + double inactive_width = 0.2; // per side + double active_width = 4.6; + double active_height = 9.6; + + double silicon_thickness = 0.01; + double copper_thickness = 0.006; + double kapton_thickness = 0.03; + double epoxy_thickness = 0.0012; + + double carbonFiberThickness = 0.01; + + double foamSpacingThickness = 1.0; + + int dist_offset = 0; + + double x_offset; + double y_offset; + + double z_offset = (face == "front") ? -foamSpacingThickness / 2.0 - carbonFiberThickness : foamSpacingThickness / 2.0 + carbonFiberThickness; + + // offset correction + if (sensor_height == 3.2 && sensor_width == 2.5) { + x_offset = 0.8; + y_offset = 1.5; + } 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; + } + + double x_condition_min = 0; + double x_condition_max = 0; + double offset_Rin_lower = 0; + double offset_Rin_upper = 0; + bool adjust_bottom_y_pos = false; + bool adjust_bottom_y_neg = false; + double x_adjust_bottom_y_pos = 0; + 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; + } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 5) { + x_condition_min = -Rin - 6; + x_condition_max = Rin; + 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; + } else if ((Rin == 5 || Rin == 7) && sensor_height == 19.2) { + x_condition_min = -Rin - 3; + x_condition_max = Rin - 0.2; + dist_offset = 2; + adjust_bottom_y_pos = false; + adjust_bottom_y_neg = false; + } else if (Rin == 5 && sensor_height == 3.2) { + x_condition_min = -(Rin + 2.6); + x_condition_max = Rin + 1.5; + 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; + } else if (Rin == 7 && sensor_height == 3.2) { + x_condition_min = -Rin - 1; + x_condition_max = Rin - 0.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; + } else if (Rin == 5 && sensor_height == 9.6 && sensor_width == 2.5) { + x_condition_min = -(Rin + 2.6); + x_condition_max = Rin; + 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; + } else if (Rin == 7 && sensor_height == 9.6 && sensor_width == 2.5) { + x_condition_min = -Rin - 2.6; + x_condition_max = Rin + 1; + dist_offset = 2; + adjust_bottom_y_pos = true; + adjust_bottom_y_neg = true; + 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 - sensor_width; + x_condition_max = Rin; + adjust_bottom_y_pos = false; + adjust_bottom_y_neg = false; + } + + offset_Rin_lower = Rin - Rin_offset; + offset_Rin_upper = Rout + Rout_offset; + + std::set> placed_sensors; + int sensor_count = 0; + + int placementCounter = 0; + bool justSkipped = false; + + std::vector X_positions; + std::vector justSkipped1; + + if (sensor_width == 2.5) { + // logic for placement - x positions with complete overlap + if (face == "front") { + X_positions = {-63.4, -60.9, -54.2, -51.7, -45.0, -42.5, -35.8, -33.3, -26.6, -24.1, -17.4, -14.9, + -8.2, -5.7, 1.0, 3.5, 10.2, 12.7, 19.4, 21.9, 28.6, 31.1, 37.8, 40.3, 47.0, 49.5, + 56.2, 58.7, 65.4}; + justSkipped1 = {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}; + } else if (face == "back") { + X_positions = {-65.5, -58.8, -56.3, -49.6, -47.1, -40.4, -37.9, -31.2, -28.7, -22.0, -19.5, -12.8, + -10.3, -3.6, -1.1, 5.6, 8.1, 14.8, 17.3, 24.0, 26.5, 33.2, 35.7, 42.4, 44.9, + 51.6, 54.1, 60.8, 63.3}; + 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 { + 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}; + } + } + } + + if (layout_type == "rectangular") { + + double x_start = -Rout; + double x_end = Rout; + + std::vector x_positions; + for (double x = x_start; x <= x_end; x += sensor_width) { + x_positions.push_back(x); + } + + int rowCounter = 0; + const int rowsToAlternate = 2; + + for (size_t i = 0; i < X_positions.size(); ++i) { + + double x = X_positions[i]; + bool justSkippedValue = justSkipped1[i]; + + std::vector y_positions_positive; + std::vector y_positions_negative; + + for (double y = -Rout - Rin_offset; y <= Rout + Rin_offset; y += sensor_height) { + std::vector> corners = { + {x, y}, + {x + sensor_width, y}, + {x, y + sensor_height}, + {x + sensor_width, y + sensor_height}}; + + bool within_bounds = std::all_of(corners.begin(), corners.end(), [&](const std::pair& corner) { + double cx = corner.first; + double cy = corner.second; + return (offset_Rin_lower <= std::sqrt(cx * cx + cy * cy) && std::sqrt(cx * cx + cy * cy) <= offset_Rin_upper); + }); + + if (within_bounds) { + if (y >= 0) { + y_positions_positive.push_back(y); + } else { + y_positions_negative.push_back(y); + } + } + } + + // adjust y positions near inner circle for positive y + if (x_condition_min <= x && x <= x_condition_max && !y_positions_positive.empty()) { + double first_y_pos = y_positions_positive.front(); + double last_y_pos = y_positions_positive.back() - sensor_height; + double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout)); + double bottom_y_pos = std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin)); + double top_distance_pos = top_y_pos - last_y_pos; + + if (adjust_bottom_y_pos && x > x_adjust_bottom_y_pos) { + bottom_y_pos = bottom_y_pos_value; + } + + double bottom_distance_pos = first_y_pos - bottom_y_pos; + + if (std::abs(top_distance_pos + bottom_distance_pos) >= sensor_height) { + for (auto& y : y_positions_positive) { + y -= bottom_distance_pos - 0.2; + } + y_positions_positive.push_back(y_positions_positive.back() + sensor_height); + } + } + + // adjust y positions near inner circle for negative y + if (x_condition_min <= x && x <= x_condition_max && !y_positions_negative.empty()) { + double first_y_neg = y_positions_negative.front(); + double last_y_neg = y_positions_negative.back() + sensor_height; + double top_y_neg = -std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout)); + double bottom_y_neg = -std::max(calculate_y_circle(x, Rin), calculate_y_circle(x + sensor_width, Rin)); + double top_distance_neg = -(top_y_neg - first_y_neg); + + if (adjust_bottom_y_neg && x > x_adjust_bottom_y_pos) { + bottom_y_neg = bottom_y_neg_value; + } + + double bottom_distance_neg = -(last_y_neg - bottom_y_neg); + + top_distance_neg = std::abs(top_distance_neg); + bottom_distance_neg = std::abs(bottom_distance_neg); + std::sort(y_positions_negative.begin(), y_positions_negative.end()); + + if (std::abs(top_distance_neg + bottom_distance_neg) >= sensor_height) { + if (sensor_height == 19.2) { + for (auto& y : y_positions_negative) { + y -= bottom_distance_neg; + } + } else { + for (auto& y : y_positions_negative) { + y += bottom_distance_neg - 0.2; + } + } + y_positions_negative.push_back(y_positions_negative.front() - sensor_height); + } + } + + // adjust positions for the rest of the disk + if ((x < x_condition_min || x > x_condition_max) && !y_positions_negative.empty() && !y_positions_positive.empty()) { + double first_y_neg = y_positions_negative.front(); + double last_y_pos = y_positions_positive.back() + sensor_height; + double top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout)); + double bottom_y_pos = -top_y_pos; + + double top_distance_pos = std::abs(top_y_pos - last_y_pos); + double bottom_distance_pos = std::abs(first_y_neg - bottom_y_pos); + + if (top_distance_pos + bottom_distance_pos >= sensor_height) { + for (auto& y : y_positions_positive) { + y += top_distance_pos - 0.2; + } + for (auto& y : y_positions_negative) { + y += top_distance_pos - 0.2; + } + double new_y = y_positions_negative.front() - sensor_height; + + if (static_cast(new_y) > static_cast(bottom_y_pos)) { + y_positions_negative.push_back(new_y); + } + } + + // Make symmetric adjustments + std::sort(y_positions_negative.begin(), y_positions_negative.end()); + std::sort(y_positions_positive.begin(), y_positions_positive.end()); + + double first_y_pos = y_positions_negative.front(); + + last_y_pos = y_positions_positive.back() + sensor_height; + + top_y_pos = std::min(calculate_y_circle(x, Rout), calculate_y_circle(x + sensor_width, Rout)); + bottom_y_pos = -top_y_pos; + top_distance_pos = std::abs(top_y_pos - last_y_pos); + bottom_distance_pos = std::abs(first_y_pos - bottom_y_pos); + + double Lb = (bottom_distance_pos + top_distance_pos) / 2; + + if (top_distance_pos < Lb) { + double shift = Lb - top_distance_pos; + for (auto& y : y_positions_negative) { + y -= shift; + } + for (auto& y : y_positions_positive) { + y -= shift; + } + } else if (top_distance_pos > Lb) { + double shift = top_distance_pos - Lb; + for (auto& y : y_positions_negative) { + y += shift; + } + for (auto& y : y_positions_positive) { + y += shift; + } + } + } + + std::vector y_positions = y_positions_positive; + y_positions.insert(y_positions.end(), y_positions_negative.begin(), y_positions_negative.end()); + + for (double y : y_positions) { + + int SiColor; + double R_material_threshold = 0; + + if (placed_sensors.find({x, y}) == placed_sensors.end()) { + placed_sensors.insert({x, y}); + TGeoVolume* sensor; + + double inactive_width = (sensor_width - active_width) / 2; + double left_inactive_x_shift; + double right_inactive_x_shift; + double active_x_shift_sensor; + + if (face == "front") { + + double active_x_shift, inactive_x_shift; + + if (justSkippedValue) { + active_x_shift = x + inactive_width / 2; + active_x_shift_sensor = active_x_shift + inactive_width; + + inactive_x_shift = x - active_width / 2 + inactive_width / 2; + } else { + active_x_shift = x - inactive_width / 2; + active_x_shift_sensor = active_x_shift - inactive_width; + + inactive_x_shift = x + active_width / 2 - inactive_width / 2; + } + + double inactive_x_shift_left, inactive_x_shift_right; + + if (sensor_width == 5.0) { + + inactive_x_shift_left = x - sensor_width / 2 + inactive_width; + inactive_x_shift_right = x + sensor_width / 2; + } + + std::vector> corners_shifted = { + {x, y}, + {x + sensor_width, y}, + {x, y + sensor_height}, + {x + sensor_width, y + sensor_height}}; + + bool within_bounds = true; + for (const auto& corner : corners_shifted) { + double cx = corner.first; + double cy = corner.second; + double dist = std::sqrt(cx * cx + cy * cy); + + if (Rin > dist || dist >= Rout) { + within_bounds = false; + break; + } + } + + if (within_bounds) { + + double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset); + + if (r_squared < R_material_threshold * R_material_threshold) { + silicon_thickness = 0.005; + copper_thickness = 0.00475; + kapton_thickness = 0.03; + epoxy_thickness = 0.0012; + + SiColor = kOrange; + } else { + silicon_thickness = 0.01; + copper_thickness = 0.006; + kapton_thickness = 0.03; + epoxy_thickness = 0.0012; + + SiColor = kGreen; + } + + if (sensor_width == 2.5) { + // silicon + std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2); + sensor->SetLineColor(SiColor); + sensor->SetFillColorAlpha(SiColor, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2)); + + std::string inactive_name = "FT3inactive_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2)); + + } else { + + std::string sensor_name = "FT3Sensor_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(SiColor); + sensor->SetFillColorAlpha(SiColor, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x + inactive_width / 2, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2)); + + std::string inactive_name_left = "FT3inactive_left_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2)); + + std::string inactive_name_right = "FT3inactive_right_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness - silicon_thickness / 2)); + } + + // silicon-to-FPC epoxy glue + std::string glue_up_name = "FT3glue_up_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2); + sensor->SetLineColor(kBlue); + sensor->SetFillColorAlpha(kBlue, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness - epoxy_thickness / 2)); + + if (r_squared < R_material_threshold * R_material_threshold) { + std::string alu_name = "FT3aluminum_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2); + sensor->SetLineColor(kBlack); + sensor->SetFillColorAlpha(kBlack, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2)); + + } else { + std::string copper_name = "FT3copper_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2); + sensor->SetLineColor(kBlack); + sensor->SetFillColorAlpha(kBlack, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness - copper_thickness / 2)); + } + + // kapton + std::string fpc_name = "FT3fpc_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2); + sensor->SetLineColor(kGreen); + sensor->SetFillColorAlpha(kGreen, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset - epoxy_thickness - kapton_thickness / 2)); + + // FPC-to-support epoxy glue + std::string glue_down_name = "FT3glue_down_front_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2); + sensor->SetLineColor(kBlue); + sensor->SetFillColorAlpha(kBlue, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset - epoxy_thickness / 2)); + } + } else { + double x_shifted = x; + double inactive_x_shift, active_x_shift; + double active_x_shift_sensor; + + if (justSkippedValue) { + active_x_shift = x + inactive_width / 2; + active_x_shift_sensor = active_x_shift + inactive_width; + + inactive_x_shift = x - active_width / 2 + inactive_width / 2; + } else { + active_x_shift = x - inactive_width / 2; + active_x_shift_sensor = active_x_shift - inactive_width; + + inactive_x_shift = x + active_width / 2 - inactive_width / 2; + } + + double inactive_x_shift_left, inactive_x_shift_right; + + if (sensor_width == 5.0) { + + inactive_x_shift_left = x - sensor_width / 2 + inactive_width; + inactive_x_shift_right = x + sensor_width / 2; + } + + std::vector> corners_shifted = { + {x_shifted, y}, + {x_shifted + sensor_width, y}, + {x_shifted, y + sensor_height}, + {x_shifted + sensor_width, y + sensor_height}}; + + bool within_bounds = true; + for (const auto& corner : corners_shifted) { + double cx = corner.first; + double cy = corner.second; + double dist = std::sqrt(cx * cx + cy * cy); + + if (Rin > dist + dist_offset || dist >= Rout) { + within_bounds = false; + break; + } + } + + if (within_bounds) { + + double r_squared = (x + x_offset) * (x + x_offset) + (y + y_offset) * (y + y_offset); + + if (r_squared < R_material_threshold * R_material_threshold) { + silicon_thickness = 0.005; + copper_thickness = 0.00475; // thinner -> + replaced by alu + kapton_thickness = 0.03; + epoxy_thickness = 0.0006; + + SiColor = kOrange; + } else { + silicon_thickness = 0.01; + copper_thickness = 0.006; + kapton_thickness = 0.03; + epoxy_thickness = 0.0012; + + SiColor = kGreen; + } + + // FPC-to-support epoxy glue + std::string glue_down_name = "FT3glue_down_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(glue_down_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2); + sensor->SetLineColor(kBlue); + sensor->SetFillColorAlpha(kBlue, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness / 2)); + + // Kapton + std::string fpc_name = "FT3fpc_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(fpc_name.c_str(), kaptonMed, sensor_width / 2, sensor_height / 2, kapton_thickness / 2); + sensor->SetLineColor(kGreen); + sensor->SetFillColorAlpha(kGreen, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness / 2)); + + if (r_squared < R_material_threshold * R_material_threshold) { + // replace copper with alu + std::string alu_name = "FT3aluminum_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(alu_name.c_str(), AluminumMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2); + sensor->SetLineColor(kBlack); + sensor->SetFillColorAlpha(kBlack, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2)); + + } else { + std::string copper_name = "FT3copper_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(copper_name.c_str(), copperMed, sensor_width / 2, sensor_height / 2, copper_thickness / 2); + sensor->SetLineColor(kBlack); + sensor->SetFillColorAlpha(kBlack, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness / 2)); + } + + // silicon-to-FPC epoxy glue + std::string glue_up_name = "FT3glue_up_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(glue_up_name.c_str(), epoxyMed, sensor_width / 2, sensor_height / 2, epoxy_thickness / 2); + sensor->SetLineColor(kBlue); + sensor->SetFillColorAlpha(kBlue, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + active_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness / 2)); + + if (sensor_width == 2.5) { + + std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, active_height / 2, silicon_thickness / 2); + sensor->SetLineColor(SiColor); + sensor->SetFillColorAlpha(SiColor, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(active_x_shift_sensor + x_offset, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2)); + + std::string inactive_name = "FT3inactive_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name.c_str(), siliconMed, (sensor_width - active_width) / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2)); + + } else { + // active (4.6 cm centered) + std::string sensor_name = "FT3Sensor_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(sensor_name.c_str(), siliconMed, active_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(SiColor); + sensor->SetFillColorAlpha(SiColor, 0.4); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + x_shifted + inactive_width / 2, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2)); + + // left inactive strip + std::string inactive_name_left = "FT3inactive_left_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name_left.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_left, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2)); + + // right inactive strip + std::string inactive_name_right = "FT3inactive_right_back_" + std::to_string(layerNumber) + "_" + std::to_string(direction) + "_" + std::to_string(sensor_count); + sensor = geoManager->MakeBox(inactive_name_right.c_str(), siliconMed, inactive_width / 2, sensor_height / 2, silicon_thickness / 2); + sensor->SetLineColor(kRed); + sensor->SetFillColorAlpha(kRed, 1.0); + motherVolume->AddNode(sensor, sensor_count++, new TGeoTranslation(x_offset + inactive_x_shift_right, y + y_offset, mZ + z_offset + epoxy_thickness + kapton_thickness + copper_thickness + epoxy_thickness + silicon_thickness / 2)); + } + } + } + } + } + + rowCounter++; + } + } + LOG(debug) << "FT3Module: done create_layout"; +} + +void FT3Module::createModule(double mZ, int layerNumber, int direction, double Rin, double Rout, double overlap, const std::string& face, const std::string& layout_type, TGeoVolume* motherVolume) +{ + + LOG(debug) << "FT3Module: createModule - Layer " << layerNumber << ", Direction " << direction << ", Face " << face; + 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/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h similarity index 71% rename from Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h index 42b96786af27a..105504e7c9fe6 100644 --- a/Detectors/ITSMFT/ITS/workflow/include/ITSWorkflow/ClusterWriterSpec.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h @@ -9,23 +9,23 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @file ClusterWriterSpec.h +/// @file TrackWriterSpec.h -#ifndef O2_ITS_CLUSTERWRITER -#define O2_ITS_CLUSTERWRITER +#ifndef O2_TRK_TRACKWRITER +#define O2_TRK_TRACKWRITER #include "Framework/DataProcessorSpec.h" namespace o2 { -namespace its +namespace trk { /// create a processor spec -/// write ITS clusters to ROOT file -framework::DataProcessorSpec getClusterWriterSpec(bool useMC); +/// write TRK tracks to ROOT file +o2::framework::DataProcessorSpec getTrackWriterSpec(bool useMC); -} // namespace its +} // namespace trk } // namespace o2 -#endif /* O2_ITS_CLUSTERWRITER */ +#endif /* O2_TRK_TRACKWRITER */ diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h similarity index 51% rename from Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h rename to Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h index 3c82a4fd7b89d..c1e7e051fb3f1 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/TrackerSpec.h +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/include/ALICE3GlobalReconstructionWorkflow/TrackerSpec.h @@ -19,13 +19,23 @@ #include "Framework/DataProcessorSpec.h" #include "Framework/Task.h" +#include + +#include "ITStracking/BoundedAllocator.h" +#include "ITStracking/ExternalAllocator.h" #include "ITStracking/TrackingInterface.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesConfig.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TStopwatch.h" +#include + +#include +#include +#include + namespace o2::trk { class TrackerDPL : public framework::Task @@ -33,24 +43,43 @@ class TrackerDPL : public framework::Task public: TrackerDPL(std::shared_ptr gr, bool isMC, - gpu::GPUDataTypes::DeviceType dType = gpu::GPUDataTypes::DeviceType::CPU); + 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; void run(framework::ProcessingContext& pc) final; 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, 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 */ \ No newline at end of file +#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/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx new file mode 100644 index 0000000000000..9827c2fc2469d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/GlobalReconstruction/workflow/src/TrackWriterSpec.cxx @@ -0,0 +1,57 @@ +// 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 TrackWriterSpec.cxx + +#include + +#include "ALICE3GlobalReconstructionWorkflow/TrackWriterSpec.h" +#include "DPLUtils/MakeRootTreeWriterSpec.h" +#include "DataFormatsITS/TrackITS.h" +#include "SimulationDataFormat/MCCompLabel.h" + +using namespace o2::framework; + +namespace o2 +{ +namespace trk +{ + +template +using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; +using LabelsType = std::vector; +using namespace o2::header; + +DataProcessorSpec getTrackWriterSpec(bool useMC) +{ + // Spectators for logging + auto tracksSize = std::make_shared(0); + auto tracksSizeGetter = [tracksSize](std::vector const& tracks) { + *tracksSize = tracks.size(); + }; + auto logger = [tracksSize]() { + LOG(info) << "TRKTrackWriter pulled " << *tracksSize << " tracks"; + }; + + return MakeRootTreeWriterSpec("trk-track-writer", + "o2trac_trk.root", + MakeRootTreeWriterSpec::TreeAttributes{"o2sim", "Tree with TRK tracks"}, + BranchDefinition>{InputSpec{"tracks", "TRK", "TRACKS", 0}, + "TRKTrack", + tracksSizeGetter}, + BranchDefinition{InputSpec{"labels", "TRK", "TRACKSMCTR", 0}, + "TRKTrackMCTruth", + (useMC ? 1 : 0), // one branch if mc labels enabled + ""})(); +} + +} // namespace trk +} // namespace o2 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/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h new file mode 100644 index 0000000000000..8a167df4d6c7b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/DataFormatsIOTOF/src/DataFormatsIOTOFLinkDef.h @@ -0,0 +1,20 @@ +// 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. + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#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 new file mode 100644 index 0000000000000..e52b5e2379e9c --- /dev/null +++ b/Detectors/Upgrades/ALICE3/IOTOF/README.md @@ -0,0 +1,38 @@ + + +# ALICE 3 TOF system + +This is top page for the TOF detector documentation. + + +## Specific detector setup + + +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 | +| `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 +```bash +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TF3 \ + --configKeyValues "IOTOFBase.detectorPattern=v3b1a;IOTOFBase.segmentedInnerTOF=true;IOTOFBase.segmentedOuterTOF=true;FT3Base.geoModel=1;FT3Base.nLayers=1;IOTOFBase.enableOuterTOF=false;IOTOFBase.enableBackwardTOF=false;IOTOFBase.enableForwardTOF=false;" +``` + + diff --git a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/GeometryTGeo.h index 177426e8dba09..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); @@ -32,11 +34,15 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache // Inner TOF static const char* getITOFLayerPattern() { return sITOFLayerName.c_str(); } + static const char* getITOFStavePattern() { return sITOFStaveName.c_str(); } + static const char* getITOFModulePattern() { return sITOFModuleName.c_str(); } static const char* getITOFChipPattern() { return sITOFChipName.c_str(); } static const char* getITOFSensorPattern() { return sITOFSensorName.c_str(); } // Outer TOF static const char* getOTOFLayerPattern() { return sOTOFLayerName.c_str(); } + static const char* getOTOFStavePattern() { return sOTOFStaveName.c_str(); } + static const char* getOTOFModulePattern() { return sOTOFModuleName.c_str(); } static const char* getOTOFChipPattern() { return sOTOFChipName.c_str(); } static const char* getOTOFSensorPattern() { return sOTOFSensorName.c_str(); } @@ -75,17 +81,39 @@ 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; // Inner TOF static std::string sITOFLayerName; + static std::string sITOFStaveName; + static std::string sITOFModuleName; static std::string sITOFChipName; static std::string sITOFSensorName; // Outer TOF static std::string sOTOFLayerName; + static std::string sOTOFStaveName; + static std::string sOTOFModuleName; static std::string sOTOFChipName; static std::string sOTOFSensorName; @@ -99,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 10d8c5ced94dd..c4cf5fd8844a8 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/base/include/IOTOFBase/IOTOFBaseParam.h @@ -20,11 +20,37 @@ 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; - bool enableOuterTOF = true; - bool enableForwardTOF = true; - bool enableBackwardTOF = true; + bool enableInnerTOF = true; // Enable Inner TOF layer + bool enableOuterTOF = true; // Enable Outer TOF layer + bool enableForwardTOF = true; // Enable Forward TOF layer + bool enableBackwardTOF = true; // Enable Backward TOF layer + std::string detectorPattern = ""; // Layouts of the detector + 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"); }; @@ -32,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 8c29127a5e7d6..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 @@ -23,11 +24,15 @@ std::string GeometryTGeo::sIOTOFVolumeName = "IOTOFV"; // Inner TOF std::string GeometryTGeo::sITOFLayerName = "ITOFLayer"; +std::string GeometryTGeo::sITOFStaveName = "ITOFStave"; +std::string GeometryTGeo::sITOFModuleName = "ITOFModule"; std::string GeometryTGeo::sITOFChipName = "ITOFChip"; std::string GeometryTGeo::sITOFSensorName = "ITOFSensor"; // Outer TOF std::string GeometryTGeo::sOTOFLayerName = "OTOFLayer"; +std::string GeometryTGeo::sOTOFStaveName = "OTOFStave"; +std::string GeometryTGeo::sOTOFModuleName = "OTOFModule"; std::string GeometryTGeo::sOTOFChipName = "OTOFChip"; std::string GeometryTGeo::sOTOFSensorName = "OTOFSensor"; @@ -51,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()) { @@ -62,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 1f3b2f4fe9fac..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); + 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 b7cc0a05c1c2e..dc9fedf439a11 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/include/IOTOFSimulation/Layer.h @@ -14,6 +14,8 @@ #include #include +#include +#include namespace o2 { @@ -23,7 +25,8 @@ class Layer { public: Layer() = default; - Layer(std::string layerName, float rInn, float rOut, float zLength, float zOffset, float layerX2X0, bool isBarrel = true); + 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, float sensorThickness = 0.0f); ~Layer() = default; auto getInnerRadius() const { return mInnerRadius; } @@ -33,9 +36,14 @@ class Layer auto getx2X0() const { return mX2X0; } auto getChipThickness() const { return mChipThickness; } auto getName() const { return mLayerName; } - auto getIsBarrel() const { return mIsBarrel; } + auto getLayout() const { return mLayout; } + auto getSegments() const { return mStaves; } + static constexpr int kBarrel = 0; + static constexpr int kDisk = 1; + static constexpr int kBarrelSegmented = 2; + static constexpr int kDiskSegmented = 3; - virtual void createLayer(TGeoVolume* motherVolume){}; + virtual void createLayer(TGeoVolume* motherVolume) {}; protected: std::string mLayerName; @@ -44,8 +52,13 @@ class Layer float mZLength; float mZOffset{0.f}; // Of use when fwd layers float mX2X0; - float mChipThickness; - bool mIsBarrel{true}; + 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 + double mTiltAngle{0.0}; // Tilt angle in degrees to be applied as a rotation around the local center of the stave }; class ITOFLayer : public Layer @@ -53,6 +66,7 @@ class ITOFLayer : public Layer public: using Layer::Layer; virtual void createLayer(TGeoVolume* motherVolume) override; + static std::vector mRegister; }; class OTOFLayer : public Layer @@ -60,6 +74,7 @@ class OTOFLayer : public Layer public: using Layer::Layer; virtual void createLayer(TGeoVolume* motherVolume) override; + static std::vector mRegister; }; class FTOFLayer : public Layer @@ -78,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 a2bba7cc5fe35..ab9a68bd401ec 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Detector.cxx @@ -20,8 +20,6 @@ #include "IOTOFSimulation/Detector.h" #include "IOTOFBase/IOTOFBaseParam.h" -using o2::itsmft::Hit; - namespace o2 { namespace iotof @@ -40,7 +38,10 @@ Detector::Detector(bool active) mHits(o2::utils::createSimVector()) { auto& iotofPars = IOTOFBaseParam::Instance(); - configLayers(iotofPars.enableInnerTOF, iotofPars.enableOuterTOF, iotofPars.enableForwardTOF); + configLayers(iotofPars.enableInnerTOF, iotofPars.enableOuterTOF, + iotofPars.enableForwardTOF, iotofPars.enableBackwardTOF, + iotofPars.detectorPattern, + iotofPars.segmentedInnerTOF, iotofPars.segmentedOuterTOF, iotofPars.x2x0); } Detector::~Detector() @@ -56,19 +57,70 @@ void Detector::ConstructGeometry() createGeometry(); } -void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof) +void Detector::configLayers(bool itof, bool otof, bool ftof, bool btof, std::string pattern, bool itofSegmented, bool otofSegmented, + const float x2x0, const float sensorThickness) { - if (itof) { - mITOFLayer = ITOFLayer(std::string{GeometryTGeo::getITOFLayerPattern()}, 19.f, 0.f, 124.f, 0.f, 0.02f, true); // iTOF + + const std::pair dInnerTof = {21.f, 129.f}; // Radius and length + std::pair dOuterTof = {92.f, 680.f}; // Radius and length + std::pair radiusRangeDiskTof = {15.f, 100.f}; + float zForwardTof = 370.f; + LOG(info) << "Configuring IOTOF layers with '" << pattern << "' pattern"; + if (pattern == "") { + LOG(info) << "Default pattern"; + } else if (pattern == "v3b") { + ftof = false; + btof = false; + } else if (pattern == "v3b1a") { + dOuterTof.second = 500.f; + zForwardTof = 270.f; + radiusRangeDiskTof = {30.f, 100.f}; + } else if (pattern == "v3b1b") { + dOuterTof.second = 500.f; + zForwardTof = 200.f; + radiusRangeDiskTof = {20.f, 68.f}; + } else if (pattern == "v3b2a") { + dOuterTof.second = 440.f; + zForwardTof = 270.f; + radiusRangeDiskTof = {30.f, 120.f}; + } else if (pattern == "v3b2b") { + dOuterTof.second = 440.f; + zForwardTof = 200.f; + radiusRangeDiskTof = {20.f, 68.f}; + } else if (pattern == "v3b3") { + dOuterTof.second = 580.f; + zForwardTof = 200.f; + radiusRangeDiskTof = {20.f, 68.f}; + } else { + LOG(fatal) << "IOTOF layer pattern " << pattern << " not recognized, exiting"; } - if (otof) { - mOTOFLayer = OTOFLayer(std::string{GeometryTGeo::getOTOFLayerPattern()}, 85.f, 0.f, 680.f, 0.f, 0.02f, true); // oTOF + 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 ? 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, itofSegmented ? ITOFLayer::kBarrelSegmented : ITOFLayer::kBarrel, + nStaves, staveWidth, staveTiltAngle, modulesPerStave, itofSegmented ? sensorThickness : 0.0f); + } + if (otof) { // oTOF + const std::string name = GeometryTGeo::getOTOFLayerPattern(); + const int nStaves = otofSegmented ? 62 : 0; // number of staves in segmented case + const double staveWidth = otofSegmented ? 9.74 : 0.0; // cm + 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, otofSegmented ? OTOFLayer::kBarrelSegmented : OTOFLayer::kBarrel, + nStaves, staveWidth, staveTiltAngle, modulesPerStave, otofSegmented ? sensorThickness : 0.0f); } if (ftof) { - mFTOFLayer = FTOFLayer(std::string{GeometryTGeo::getFTOFLayerPattern()}, 15.f, 100.f, 0.f, 370.f, 0.02f, false); // fTOF + const std::string name = GeometryTGeo::getFTOFLayerPattern(); + mFTOFLayer = FTOFLayer(name, radiusRangeDiskTof.first, radiusRangeDiskTof.second, 0.f, zForwardTof, x2x0, FTOFLayer::kDisk); // fTOF } if (btof) { - mBTOFLayer = BTOFLayer(std::string{GeometryTGeo::getBTOFLayerPattern()}, 15.f, 100.f, 0.f, -370.f, 0.02f, false); // bTOF + const std::string name = GeometryTGeo::getBTOFLayerPattern(); + mBTOFLayer = BTOFLayer(name, radiusRangeDiskTof.first, radiusRangeDiskTof.second, 0.f, -zForwardTof, x2x0, BTOFLayer::kDisk); // bTOF } } @@ -148,24 +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) { - v = geoManager->GetVolume(GeometryTGeo::getITOFSensorPattern()); - LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); - AddSensitiveVolume(v); + 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"; } - if (iotofPars.enableOuterTOF) { - v = geoManager->GetVolume(GeometryTGeo::getOTOFSensorPattern()); - LOGP(info, "Adding IOTOF Sensitive Volume {}", v->GetName()); - AddSensitiveVolume(v); + + // 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.enableForwardTOF) { + 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 (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); @@ -214,28 +289,28 @@ bool Detector::ProcessHits(FairVolume* vol) bool startHit = false, stopHit = false; unsigned char status = 0; if (fMC->IsTrackEntering()) { - status |= Hit::kTrackEntering; + status |= o2::itsmft::Hit::kTrackEntering; } if (fMC->IsTrackInside()) { - status |= Hit::kTrackInside; + status |= o2::itsmft::Hit::kTrackInside; } if (fMC->IsTrackExiting()) { - status |= Hit::kTrackExiting; + status |= o2::itsmft::Hit::kTrackExiting; } if (fMC->IsTrackOut()) { - status |= Hit::kTrackOut; + status |= o2::itsmft::Hit::kTrackOut; } if (fMC->IsTrackStop()) { - status |= Hit::kTrackStopped; + status |= o2::itsmft::Hit::kTrackStopped; } if (fMC->IsTrackAlive()) { - status |= Hit::kTrackAlive; + status |= o2::itsmft::Hit::kTrackAlive; } // track is entering or created in the volume - if ((status & Hit::kTrackEntering) || (status & Hit::kTrackInside && !mTrackData.mHitStarted)) { + if ((status & o2::itsmft::Hit::kTrackEntering) || (status & o2::itsmft::Hit::kTrackInside && !mTrackData.mHitStarted)) { startHit = true; - } else if ((status & (Hit::kTrackExiting | Hit::kTrackOut | Hit::kTrackStopped))) { + } else if ((status & (o2::itsmft::Hit::kTrackExiting | o2::itsmft::Hit::kTrackOut | o2::itsmft::Hit::kTrackStopped))) { stopHit = true; } @@ -258,15 +333,31 @@ 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); - - Hit* p = addHit(stack->GetCurrentTrackNumber(), lay, mTrackData.mPositionStart.Vect(), positionStop.Vect(), - mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), - mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status); + 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(), sensorID, mTrackData.mPositionStart.Vect(), positionStop.Vect(), + mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), + mTrackData.mEnergyLoss, mTrackData.mTrkStatusStart, status); // RS: not sure this is needed // Increment number of Detector det points in TParticle @@ -286,4 +377,4 @@ o2::itsmft::Hit* Detector::addHit(int trackID, int detID, const TVector3& startP } // namespace iotof } // namespace o2 -ClassImp(o2::iotof::Detector); \ No newline at end of file +ClassImp(o2::iotof::Detector); 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 53c07d1fa4978..f2e42e1bce172 100644 --- a/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx +++ b/Detectors/Upgrades/ALICE3/IOTOF/simulation/src/Layer.cxx @@ -14,84 +14,406 @@ #include "Framework/Logger.h" +#include +#include #include #include +#include + +#include +#include namespace o2 { namespace iotof { -Layer::Layer(std::string layerName, float rInn, float rOut, float zLength, float zOffset, float layerX2X0, bool isBarrel) - : mLayerName(layerName), mInnerRadius(rInn), mOuterRadius(rOut), mZLength(zLength), mZOffset(zOffset), mX2X0(layerX2X0), mIsBarrel(isBarrel) +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, 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) { - float Si_X0 = 9.5f; + const float Si_X0 = 9.5f; // cm, radiation length of silicon mChipThickness = mX2X0 * Si_X0; - if (isBarrel) { - mOuterRadius = mInnerRadius + mChipThickness; - } else { - mZLength = mChipThickness; + std::string name = ""; + switch (layout) { + case kBarrel: + case kBarrelSegmented: + name = "barrel"; + mOuterRadius = mInnerRadius + mChipThickness; + break; + case kDisk: + case kDiskSegmented: + name = "forward"; + mZLength = mChipThickness; + break; + default: + LOG(fatal) << "Invalid layout " << layout; + } + // Sanity checks + if (mInnerRadius > mOuterRadius) { + LOG(fatal) << "Invalid layer dimensions: rInner " << mInnerRadius << " cm is larger than rOuter " << mOuterRadius << " cm"; + } + if ((mStaves.first != 0 || mStaves.second != 0.0f) && (layout != kBarrelSegmented && layout != kDiskSegmented)) { + LOG(fatal) << "Invalid configuration: number of segments " << mStaves.first << " is set for non-segmented layout " << layout; + } + if ((mStaves.first <= 1 || mStaves.second <= 0.0f) && (layout == kBarrelSegmented || layout == kDiskSegmented)) { + LOG(fatal) << "Invalid configuration: number of segments " << mStaves.first << " must be positive for segmented layout " << layout; + } + if (mModulesPerStave <= 0 && (layout == kBarrelSegmented || layout == kDiskSegmented)) { + LOG(fatal) << "Invalid configuration: number of sensors per segment " << mModulesPerStave << " must be positive for segmented layout " << layout; + } + if (std::abs(mTiltAngle) > 0.1 && (layout != kBarrelSegmented && layout != kDiskSegmented)) { + LOG(fatal) << "Invalid configuration: tilt angle " << mTiltAngle << " is set for non-segmented layout " << layout; + } + 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"; } - LOGP(info, "TOF: Creating {} layer: rInner: {} (cm) rOuter: {} (cm) zLength: {} (cm) zOffset: {} x2X0: {}", isBarrel ? std::string("barrel") : std::string("forward"), mInnerRadius, mOuterRadius, mZLength, mZOffset, mX2X0); + 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); } -void ITOFLayer::createLayer(TGeoVolume* motherVolume) +void setLayerStyle(TGeoVolume* obj) +{ + obj->SetLineColor(kRed - 7); + obj->SetFillColor(kRed - 7); + obj->SetLineWidth(1); + obj->SetTransparency(70); +} +void setStaveStyle(TGeoVolume* obj) +{ + obj->SetLineColor(kRed - 5); + obj->SetFillColor(kRed - 9); + obj->SetLineWidth(2); + obj->SetTransparency(45); +} +void setModuleStyle(TGeoVolume* obj) +{ + obj->SetLineColor(kRed - 3); + obj->SetFillColor(kRed - 8); + obj->SetLineWidth(2); + obj->SetTransparency(35); +} +void setChipStyle(TGeoVolume* obj) { - std::string chipName = o2::iotof::GeometryTGeo::getITOFChipPattern(), - sensName = o2::iotof::GeometryTGeo::getITOFSensorPattern(); + obj->SetLineColor(kOrange); + obj->SetFillColor(kOrange - 9); + obj->SetLineWidth(3); + obj->SetTransparency(15); +} +void setSensorStyle(TGeoVolume* obj) +{ + obj->SetLineColor(kRed); + obj->SetFillColor(kRed - 9); + obj->SetLineWidth(3); + obj->SetTransparency(5); +} - TGeoTube* sensor = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); - TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); - TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); +std::vector ITOFLayer::mRegister; +void ITOFLayer::createLayer(TGeoVolume* motherVolume) +{ + const char* chipName = o2::iotof::GeometryTGeo::getITOFChipPattern(); + const char* sensName = o2::iotof::GeometryTGeo::getITOFSensorPattern(); + const char* moduleName = o2::iotof::GeometryTGeo::getITOFModulePattern(); + const char* staveName = o2::iotof::GeometryTGeo::getITOFStavePattern(); TGeoMedium* medSi = gGeoManager->GetMedium("TF3_SILICON$"); TGeoMedium* medAir = gGeoManager->GetMedium("TF3_AIR$"); - LOGP(info, "Media: {} {}", (void*)medSi, (void*)medAir); - TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); - TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - sensVol->SetLineColor(kRed + 3); - chipVol->SetLineColor(kRed + 3); - layerVol->SetLineColor(kRed + 3); - - LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", chipVol->GetName(), layerVol->GetName()); - layerVol->AddNode(chipVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); - motherVolume->AddNode(layerVol, 1, nullptr); + switch (mLayout) { + case kBarrel: { + TGeoTube* sensor = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + + TGeoVolume* sensVol = new TGeoVolume(sensName, sensor, medSi); + TGeoVolume* chipVol = new TGeoVolume(chipName, chip, medSi); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + setSensorStyle(sensVol); + setChipStyle(chipVol); + setLayerStyle(layerVol); + + LOGP(info, "Inserting Barrel {} in {} ", sensVol->GetName(), chipVol->GetName()); + ITOFLayer::mRegister.push_back(sensVol->GetName()); + chipVol->AddNode(sensVol, 1, nullptr); + + LOGP(info, "Inserting Barrel {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(chipVol, 1, nullptr); + + LOGP(info, "Inserting Barrel {} in {} ", layerVol->GetName(), motherVolume->GetName()); + motherVolume->AddNode(layerVol, 1, nullptr); + return; + } + 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 + 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); + + // Now we create the volume for a single stave + TGeoBBox* stave = new TGeoBBox(staveSizeX * 0.5, staveSizeY * 0.5, staveSizeZ * 0.5); + TGeoVolume* staveVol = new TGeoVolume(staveName, stave, medAir); + 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 + TGeoBBox* module = new TGeoBBox(moduleSizeX * 0.5, moduleSizeY * 0.5, moduleSizeZ * 0.5); + TGeoVolume* moduleVol = new TGeoVolume(moduleName, module, medAir); + setModuleStyle(moduleVol); + + // Now we create the volume of the chip, which is the same for all modules + const int chipsPerModuleX = 2; // we assume that each module is divided in 2 chips along the x direction + const int chipsPerModuleZ = 2; // we assume that each module is divided in 2 chips along the z direction + const double chipSizeX = moduleSizeX / chipsPerModuleX; // cm + const double chipSizeY = moduleSizeY; // cm + const double chipSizeZ = moduleSizeZ / chipsPerModuleZ; // cm + TGeoBBox* chip = new TGeoBBox(chipSizeX * 0.5, chipSizeY * 0.5, chipSizeZ * 0.5); + TGeoVolume* chipVol = new TGeoVolume(chipName, chip, medSi); + setChipStyle(chipVol); + + // Finally we create the volume of the sensor, which is the same for all chips + 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 = 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); + setSensorStyle(sensVol); + ITOFLayer::mRegister.push_back(sensVol->GetName()); + + // Now we build a chip from sensors + for (int i = 0; i < sensorsPerChipX; ++i) { + for (int j = 0; j < sensorsPerChipZ; ++j) { + LOGP(info, "iTOF: Creating sensor {}/{} for chip {}/{}", i + 1, sensorsPerChipX, j + 1, sensorsPerChipZ); + auto* translation = new TGeoTranslation((i + 0.5) * sensorSizeX - 0.5 * chipSizeX, + 0, + (j + 0.5) * sensorSizeZ - 0.5 * chipSizeZ); + chipVol->AddNode(sensVol, 1 + i * sensorsPerChipZ + j, translation); + } + } + + // Now we build a module from chips + for (int i = 0; i < chipsPerModuleX; ++i) { + for (int j = 0; j < chipsPerModuleZ; ++j) { + LOGP(info, "iTOF: Creating chip {}/{} for module {}/{}", i + 1, chipsPerModuleX, j + 1, chipsPerModuleZ); + auto* translation = new TGeoTranslation((i + 0.5) * chipSizeX - 0.5 * moduleSizeX, 0, (j + 0.5) * chipSizeZ - 0.5 * moduleSizeZ); + moduleVol->AddNode(chipVol, 1 + i * chipsPerModuleZ + j, translation); + } + } + + // Now we build a stave from modules + for (int i = 0; i < modulesPerStaveX; ++i) { + for (int j = 0; j < mModulesPerStave; ++j) { + LOGP(info, "iTOF: 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); + } + } + + // We finally put all the staves in the layer + for (int i = 0; i < mStaves.first; ++i) { + LOGP(info, "iTOF: Creating stave {}/{} for layer {}", i + 1, mStaves.first, layerVol->GetName()); + const double phi = TMath::TwoPi() * i / mStaves.first; + const double x = avgRadius * TMath::Cos(phi); + const double y = avgRadius * TMath::Sin(phi); + auto* rotation = new TGeoRotation(Form("segmentRot%d", i + 1), phi * TMath::RadToDeg() + 90 + mTiltAngle, 0, 0); + auto* transformation = new TGeoCombiTrans(x, y, 0, rotation); + + LOGP(info, "Inserting Barrel {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(staveVol, 1 + i, transformation); + } + LOGP(info, "Inserting Barrel {} in {} at r={} cm", layerVol->GetName(), motherVolume->GetName(), avgRadius); + motherVolume->AddNode(layerVol, 1, nullptr); + return; + } + default: + LOG(fatal) << "Invalid layout " << mLayout; + } } +std::vector OTOFLayer::mRegister; void OTOFLayer::createLayer(TGeoVolume* motherVolume) { - std::string chipName = o2::iotof::GeometryTGeo::getOTOFChipPattern(), - sensName = o2::iotof::GeometryTGeo::getOTOFSensorPattern(); - - TGeoTube* sensor = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); - TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); - TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + const char* chipName = o2::iotof::GeometryTGeo::getOTOFChipPattern(); + const char* sensName = o2::iotof::GeometryTGeo::getOTOFSensorPattern(); + const char* moduleName = o2::iotof::GeometryTGeo::getOTOFModulePattern(); + const char* staveName = o2::iotof::GeometryTGeo::getOTOFStavePattern(); TGeoMedium* medSi = gGeoManager->GetMedium("TF3_SILICON$"); TGeoMedium* medAir = gGeoManager->GetMedium("TF3_AIR$"); + LOGP(info, "Media: {} {}", (void*)medSi, (void*)medAir); - TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); - TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - sensVol->SetLineColor(kRed + 3); - chipVol->SetLineColor(kRed + 3); - layerVol->SetLineColor(kRed + 3); - - LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", chipVol->GetName(), layerVol->GetName()); - layerVol->AddNode(chipVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); - motherVolume->AddNode(layerVol, 1, nullptr); + switch (mLayout) { + case kBarrel: { + TGeoTube* sensor = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + TGeoTube* chip = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + TGeoTube* layer = new TGeoTube(mInnerRadius, mOuterRadius, mZLength / 2); + + TGeoVolume* sensVol = new TGeoVolume(sensName, sensor, medSi); + TGeoVolume* chipVol = new TGeoVolume(chipName, chip, medSi); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + setSensorStyle(sensVol); + setChipStyle(chipVol); + setLayerStyle(layerVol); + + LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + OTOFLayer::mRegister.push_back(sensVol->GetName()); + chipVol->AddNode(sensVol, 1, nullptr); + + LOGP(info, "Inserting {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(chipVol, 1, nullptr); + + LOGP(info, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); + motherVolume->AddNode(layerVol, 1, nullptr); + return; + } + 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, 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); + + // Now we create the volume for a single stave + TGeoBBox* stave = new TGeoBBox(staveSizeX * 0.5, staveSizeY * 0.5, staveSizeZ * 0.5); + TGeoVolume* staveVol = new TGeoVolume(staveName, stave, medAir); + setStaveStyle(staveVol); + + // Now we create the volume for a single module (sensor + chip) + // 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); + + // Now we create the volume of the chip, which is the same for all modules + const int chipsPerModuleX = 2; // we assume that each module is divided in 2 chips along the x direction + const int chipsPerModuleZ = 2; // we assume that each module is divided in 2 chips along the z direction + const double chipSizeX = moduleSizeX / chipsPerModuleX; // cm + const double chipSizeY = moduleSizeY; // cm + const double chipSizeZ = moduleSizeZ / chipsPerModuleZ; // cm + TGeoBBox* chip = new TGeoBBox(chipSizeX * 0.5, chipSizeY * 0.5, chipSizeZ * 0.5); + TGeoVolume* chipVol = new TGeoVolume(chipName, chip, medSi); + setChipStyle(chipVol); + + // Finally we create the volume of the sensor, which is the same for all chips + 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 = 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); + setSensorStyle(sensVol); + OTOFLayer::mRegister.push_back(sensVol->GetName()); + + // Now we build a chip from sensors + for (int i = 0; i < sensorsPerChipX; ++i) { + for (int j = 0; j < sensorsPerChipZ; ++j) { + LOGP(info, "oTOF: Creating sensor {}/{} for chip {}/{}", i + 1, sensorsPerChipX, j + 1, sensorsPerChipZ); + auto* translation = new TGeoTranslation((i + 0.5) * sensorSizeX - 0.5 * chipSizeX, + 0, + (j + 0.5) * sensorSizeZ - 0.5 * chipSizeZ); + chipVol->AddNode(sensVol, 1 + i * sensorsPerChipZ + j, translation); + } + } + + // Now we build a module from chips + for (int i = 0; i < chipsPerModuleX; ++i) { + for (int j = 0; j < chipsPerModuleZ; ++j) { + LOGP(info, "oTOF: Creating chip {}/{} for module {}/{}", i + 1, chipsPerModuleX, j + 1, chipsPerModuleZ); + auto* translation = new TGeoTranslation((i + 0.5) * chipSizeX - 0.5 * moduleSizeX, 0, (j + 0.5) * chipSizeZ - 0.5 * moduleSizeZ); + moduleVol->AddNode(chipVol, 1 + i * chipsPerModuleZ + j, translation); + } + } + + // Now we build a stave from modules + for (int i = 0; i < modulesPerStaveX; ++i) { + 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); + } + } + + // We finally put all the staves in the layer + for (int i = 0; i < mStaves.first; ++i) { + LOGP(info, "oTOF: Creating stave {}/{} for layer {}", i + 1, mStaves.first, layerVol->GetName()); + const double phi = TMath::TwoPi() * i / mStaves.first; + const double x = avgRadius * TMath::Cos(phi); + const double y = avgRadius * TMath::Sin(phi); + auto* rotation = new TGeoRotation(Form("segmentRot%d", i + 1), phi * TMath::RadToDeg() + 90 + mTiltAngle, 0, 0); + auto* transformation = new TGeoCombiTrans(x, y, 0, rotation); + + LOGP(info, "Inserting Barrel {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(staveVol, 1 + i, transformation); + } + LOGP(info, "Inserting Barrel {} in {} at r={} cm", layerVol->GetName(), motherVolume->GetName(), avgRadius); + motherVolume->AddNode(layerVol, 1, nullptr); + return; + } + default: + LOG(fatal) << "Invalid layout " << mLayout; + } } void FTOFLayer::createLayer(TGeoVolume* motherVolume) @@ -109,9 +431,9 @@ void FTOFLayer::createLayer(TGeoVolume* motherVolume) TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - sensVol->SetLineColor(kRed + 3); - chipVol->SetLineColor(kRed + 3); - layerVol->SetLineColor(kRed + 3); + setSensorStyle(sensVol); + setChipStyle(chipVol); + setLayerStyle(layerVol); LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); chipVol->AddNode(sensVol, 1, nullptr); @@ -141,9 +463,9 @@ void BTOFLayer::createLayer(TGeoVolume* motherVolume) TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - sensVol->SetLineColor(kRed + 3); - chipVol->SetLineColor(kRed + 3); - layerVol->SetLineColor(kRed + 3); + setSensorStyle(sensVol); + setChipStyle(chipVol); + setLayerStyle(layerVol); LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); chipVol->AddNode(sensVol, 1, nullptr); @@ -159,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/Passive/src/Pipe.cxx b/Detectors/Upgrades/ALICE3/Passive/src/Pipe.cxx index 7dfd26a79b38d..fe0a1c50330fe 100644 --- a/Detectors/Upgrades/ALICE3/Passive/src/Pipe.cxx +++ b/Detectors/Upgrades/ALICE3/Passive/src/Pipe.cxx @@ -122,7 +122,8 @@ void Alice3Pipe::ConstructGeometry() // Add everything to the barrel barrel->AddNode(pipeVolume, 1, new TGeoTranslation(0, 30.f, 0)); - pipeVolume->SetLineColor(kGreen + 3); + pipeVolume->SetLineColor(37); + pipeVolume->SetTransparency(0); } void Alice3Pipe::createMaterials() diff --git a/Detectors/Upgrades/ALICE3/README.md b/Detectors/Upgrades/ALICE3/README.md index 7e1e1c03718d8..6ff034facb546 100644 --- a/Detectors/Upgrades/ALICE3/README.md +++ b/Detectors/Upgrades/ALICE3/README.md @@ -21,7 +21,7 @@ The specific modules for Run 5 are enabled by passing their their IDs to the `-m A list of the available DetIDs is reproted in the table below: | Detector ID | Detector description | -|-------------|----------------------------------| +| ----------- | -------------------------------- | | `A3IP` | Beam pipe | | `TRK` | Barrel Tracker | | `TF3` | Time Of Flight detectors | @@ -49,7 +49,7 @@ export ALICE3_MAGFIELD_MACRO=../ALICE3Field.C An exampling macro for a custom magnetic field is stored in `Detectors/Upgrades/macros/ALICE3Field.C`. -### Run a simple simulation for run 5 +### Run a simple simulation for ALICE 3 The simplest command to be run to test the simulation is working is: ```bash @@ -61,13 +61,36 @@ To enable a specific set of modules, e.g. the beampipe and the TOFs one can spec ```bash o2-sim-run5 -n 10 -m A3IP TF3 ``` + +#### Specific detector setups + +Configurables for various sub-detectors are presented in the following Table: + +| Available options | Link to options | +| ----------------- | ---------------------------------------------------------------- | +| TRK | [Link to TRK options](./TRK/README.md#specific-detector-setup) | +| FT3 | [Link to FT3 options](./FT3/README.md#specific-detector-setup) | +| TOF | [Link to TOF options](./IOTOF/README.md#specific-detector-setup) | + +Example O2 command to create a geometry with **segmented layers for TRK (expect for VD), FT3 and TOF:** + +```bash +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ +--configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kSegmented;FT3Base.layoutFT3=kSegmented;IOTOFBase.segmentedInnerTOF=true;IOTOFBase.segmentedOuterTOF=true" +``` + +Example O2 command to create a geometry with **simple (non-segmented) layers for TRK, FT3 and TOF**: + +```bash +o2-sim-serial-run5 -n 1 -g pythia8hi -m A3IP TRK FT3 TF3 \ +--configKeyValues "TRKBase.layoutVD=kIRISFullCyl;TRKBase.layoutMLOT=kCylindrical;FT3Base.layoutFT3=kTrapezoidal;IOTOFBase.segmentedInnerTOF=false;IOTOFBase.segmentedOuterTOF=false" +``` + ### Output of the simulation The simulation will produce a `o2sim_Hits.root` file with a tree with the hits related to that detector. -Currently, hits are produced for: `TRK`, `FT3`, and `TF3`. -More detectors will be included. ## Reconstruction WIP ## Analysis -WIP \ No newline at end of file +WIP diff --git a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt index 645e3149e4ab7..6e3437c9d841b 100644 --- a/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/CMakeLists.txt @@ -10,5 +10,7 @@ # or submit itself to any jurisdiction. add_subdirectory(base) +add_subdirectory(macros) add_subdirectory(simulation) -add_subdirectory(workflow) \ No newline at end of file +add_subdirectory(reconstruction) +add_subdirectory(workflow) diff --git a/Detectors/Upgrades/ALICE3/TRK/README.md b/Detectors/Upgrades/ALICE3/TRK/README.md index 44937fb3663a6..efe07ab092eb2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/README.md +++ b/Detectors/Upgrades/ALICE3/TRK/README.md @@ -6,5 +6,88 @@ This is top page for the TRK detector documentation. + +## Specific detector setup + + +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` 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 +```bash +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 +``` + \ No newline at end of file +/doxy --> 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 a1e4b9321130f..53ad7662cbfcd 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/GeometryTGeo.h @@ -14,6 +14,8 @@ #include #include +#include "DetectorsCommonDataFormats/DetID.h" +#include "TRKBase/TRKBaseParam.h" namespace o2 { @@ -41,13 +43,20 @@ 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(); } static const char* getTRKPetalDiskPattern() { return sPetalDiskName.c_str(); } static const char* getTRKPetalLayerPattern() { return sPetalLayerName.c_str(); } static const char* getTRKStavePattern() { return sStaveName.c_str(); } + static const char* getTRKHalfStavePattern() { return sHalfStaveName.c_str(); } + static const char* getTRKModulePattern() { return sModuleName.c_str(); } static const char* getTRKChipPattern() { return sChipName.c_str(); } static const char* getTRKSensorPattern() { return sSensorName.c_str(); } + static const char* getTRKDeadzonePattern() { return sDeadzoneName.c_str(); } + static const char* getTRKMetalStackPattern() { return sMetalStackName.c_str(); } + static const char* getTRKWrapVolPattern() { return sWrapperVolumeName.c_str(); } int getNumberOfChips() const { return mSize; } @@ -61,6 +70,8 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache int extractNumberOfChipsPerPetalVD() const; int extractNumberOfStavesMLOT(int lay) const; int extractNumberOfHalfStavesMLOT(int lay) const; + int extractNumberOfModulesMLOT(int lay) const; + int extractNumberOfChipsMLOT(int lay) const; /// Extract number following the prefix in the name string int extractVolumeCopy(const char* name, const char* prefix) const; @@ -73,33 +84,76 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache void setOwner(bool v) { mOwner = v; } void Print(Option_t* opt = "") const; - void PrintChipID(int index, int subDetID, int petalcase, int disk, int lay, int stave, int halfstave) const; + void PrintChipID(int index, int subDetID, int petalcase, int disk, int lay, int stave, int halfstave, int mod, int chip) const; - int getLayer(int index) const; - int getStave(int index) const; - int getHalfStave(int index) const; int getSubDetID(int index) const; int getPetalCase(int index) const; int getDisk(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; + int getChip(int index) const; + + void defineMLOTSensors(); + int getBarrelLayer(int) const; + + // sensor ref X and alpha for ML & OT + void extractSensorXAlphaMLOT(int, float&, float&); + + // cache for tracking frames (ML & OT) + bool isTrackingFrameCachedMLOT() const { return !mCacheRefXMLOT.empty(); } + void fillTrackingFramesCacheMLOT(); + + float getSensorRefAlphaMLOT(int chipId) const + { + 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(); + return mCacheRefAlphaMLOT[local]; + } + + float getSensorXMLOT(int chipId) const + { + 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(); + return mCacheRefXMLOT[local]; + } + + // create matrix for tracking to local frame for MLOT + TGeoHMatrix& createT2LMatrixMLOT(int); /// This routine computes the chip index number from the subDetID, petal, disk, layer, stave /// TODO: retrieve also from chip when chips will be available + /// This routine computes the chip index number from the subDetID, petal, disk, layer, stave, half stave, module, chip /// \param int subDetID The subdetector ID, 0 for VD, 1 for MLOT /// \param int petalcase The petal case number for VD, from 0 to 3 /// \param int disk The disk number for VD, from 0 to 5 /// \param int lay The layer number. Starting from 0 both for VD and MLOT /// \param int stave The stave number for MLOT. Starting from 0 /// \param int halfstave The half stave number for MLOT. Can be 0 or 1 - int getChipIndex(int subDetID, int petalcase, int disk, int lay, int stave, int halfstave) const; + /// \param int module The module number for MLOT, from 0 to 10 (or 20) + /// \param int chip The chip number for MLOT, from 0 to 8 + unsigned short getChipIndex(int subDetID, int petalcase, int disk, int lay, int stave, int halfstave, int mod, int chip) const; - /// This routine computes the chip index number from the subDetID, volume, layer, stave /// TODO: retrieve also from chip when chips will be available + /// This routine computes the chip index number from the subDetID, volume, layer, stave, half stave, module, chip /// \param int subDetID The subdetector ID, 0 for VD, 1 for MLOT /// \param int volume is needed only with the current configuration for VD where each single element is a volume. // TODO: when the geometry naming scheme will be changed, change this method /// \param int lay The layer number for the MLOT. In the current configuration for VD this is not needed. // TODO: when the geometry naming scheme will be changed, change this method /// \param int stave The stave number in each layer for MLOT. Starting from 0. /// \param int halfstave The half stave number for MLOT. Can be 0 or 1 - int getChipIndex(int subDetID, int volume, int lay, int stave, int halfstave) const; + /// \param int module The module number for MLOT, from 0 to 10 (or 20) + /// \param int chip The chip number for MLOT, from 0 to 8 + unsigned short getChipIndex(int subDetID, int volume, int lay, int stave, int halfstave, int mod, int chip) const; - /// This routine computes subDetID, petal, disk, layer, stave given the chip index number /// TODO: copute also from chip when chips will be available + /// This routine computes subDetID, petal, disk, layer, stave, half stave, module, chip, given the chip index number /// \param int index The chip index number, starting from 0 /// \param int subDetID The subdetector ID, 0 for VD, 1 for MLOT /// \param int petalcase The petal case number for VD, from 0 to 3 @@ -107,10 +161,12 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache /// \param int lay The layer number. Starting from 0 both for VD and MLOT /// \param int stave The stave number for MLOT. Starting from 0 /// \param int halfstave The half stave number for MLOT. Can be 0 or 1 - bool getChipID(int index, int& subDetID, int& petalcase, int& disk, int& lay, int& stave, int& halfstave) const; + /// \param int module The module number for MLOT, from 0 to 10 (or 20) + /// \param int chip The chip number for MLOT, from 0 to 8 + bool getChipID(int index, int& subDetID, int& petalcase, int& disk, int& lay, int& stave, int& halfstave, int& mod, int& chip) const; - int getLastChipIndex(int lay) const { return mLastChipIndex[lay]; } - int getFirstChipIndex(int lay, int petalcase, int subDetID) const + unsigned short getLastChipIndex(int lay) const { return mLastChipIndex[lay]; } + unsigned short getFirstChipIndex(int lay, int petalcase, int subDetID) const { /// Get the first chip index of the active petal (VD) or layer (MLOT) if (subDetID == 0) { // VD @@ -127,50 +183,73 @@ class GeometryTGeo : public o2::detectors::DetMatrixCache TString getMatrixPath(int index) const; +#ifdef ENABLE_UPGRADES static const char* composeSymNameTRK(int d) { return Form("%s_%d", o2::detectors::DetID(o2::detectors::DetID::TRK).getName(), d); } +#endif + static const char* composeSymNameLayer(int d, int layer); static const char* composeSymNameStave(int d, int layer); - static const char* composeSymNameChip(int d, int lr); + static const char* composeSymNameModule(int d, int layer); + static const char* composeSymNameChip(int d, int layer); static const char* composeSymNameSensor(int d, int layer); protected: 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; static std::string sPetalDiskName; static std::string sPetalLayerName; static std::string sStaveName; + static std::string sHalfStaveName; + static std::string sModuleName; static std::string sChipName; static std::string sSensorName; - static std::string sWrapperVolumeName; ///< Wrapper volume name + static std::string sDeadzoneName; + static std::string sMetalStackName; + + static std::string sWrapperVolumeName; ///< Wrapper volume name, not implemented at the moment Int_t mNumberOfLayersMLOT; ///< number of layers Int_t mNumberOfActivePartsVD; ///< number of layers Int_t mNumberOfLayersVD; ///< number of layers Int_t mNumberOfPetalsVD; ///< number of Petals = chip in each VD layer Int_t mNumberOfDisksVD; ///< number of Disks = 6 - std::vector mLastChipIndex; ///< max ID of the detctor in the petal(VD) or layer(MLOT) - std::vector mLastChipIndexVD; ///< max ID of the detctor in the layer for the VD - std::vector mLastChipIndexMLOT; ///< max ID of the detctor in the layer for the MLOT + std::vector mNumberOfStaves; ///< Number Of Staves per layer in ML/OT + std::vector mNumberOfHalfStaves; ///< Number Of Half staves in each stave of the layer in ML/OT + std::vector mNumberOfModules; ///< Number Of Modules per stave (half stave) in ML/OT + std::vector mNumberOfChips; ///< number of chips per module in ML/OT std::vector mNumberOfChipsPerLayerVD; ///< number of chips per layer VD ( = number of petals) - std::vector mNumberOfChipsPerLayerMLOT; ///< number of chips per layer MLOT ( = 1 for the moment) + std::vector mNumberOfChipsPerLayerMLOT; ///< number of chips per layer MLOT std::vector mNumbersOfChipPerDiskVD; ///< numbersOfChipPerDiskVD std::vector mNumberOfChipsPerPetalVD; ///< numbersOfChipPerPetalVD - std::vector mNumberOfStaves; ///< Number Of Staves per layer in ML/OT - std::vector mNumberOfHalfStaves; ///< Number Of Staves in each stave of the layer in ML/OT - std::array mLayerToWrapper; ///< Layer to wrapper correspondence + // std::vector mNumberOfChipsPerStave; ///< number of chips per stave in ML/OT + // std::vector mNumberOfChipsPerHalfStave; ///< number of chips per half stave in ML/OT + // std::vector mNumberOfChipsPerModule; ///< number of chips per module in ML/OT + std::vector mLastChipIndex; ///< max ID of the detctor in the petal(VD) or layer(MLOT) + std::vector mLastChipIndexVD; ///< max ID of the detctor in the layer for the VD + std::vector mLastChipIndexMLOT; ///< max ID of the detctor in the layer for the MLOT + + std::array mLayerToWrapper; ///< Layer to wrapper correspondence, not implemented yet bool mOwner = true; //! is it owned by the singleton? + std::vector sensorsMLOT; + std::vector mCacheRefXMLOT; /// cache for X of ML and OT + std::vector mCacheRefAlphaMLOT; /// cache for sensor ref alpha ML and OT + + eMLOTLayout mLayoutMLOT; // ML and OT detector layout design + private: static std::unique_ptr sInstance; }; } // namespace trk } // namespace o2 -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h index 100af5be1b4d0..7ee569c9bd8e8 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/SegmentationChip.h @@ -35,18 +35,18 @@ class SegmentationChip // The "detector coordinate system" refers to the hit position in row,col inside the sensor // This class provides the transformations from the local and detector coordinate systems // The conversion between global and local coordinate systems is operated by the transformation matrices - // For the curved VD layers there exist three coordinate systems and one is transient. + // For the curved VD layers there exist four coordinate systems. // 1. The global (curved) coordinate system. The chip's center of coordinate system is // defined at the the mid-point of the detector. - // 2. The local (flat) coordinate system. This is the tube segment projected onto a flat - // surface. In the projection we implicitly assume that the inner and outer - // stretch does not depend on the radius. - // 3. The detector coordinate system. Defined by the row and column segmentation - // defined at the upper edge in the flat coord. + // 2. The local (curved) coordinate system, centered in 0,0,0. + // 3. The local (flat) coordinate system. This is the tube segment projected onto a flat + // surface, centered in the middle of the chip, with the y axis pointing towards the interaction point. + // In the projection we implicitly assume that the inner and outer stretch does not depend on the radius. + // 4. The detector coordinate system. Defined by the row and column segmentation. // For the flat ML and OT layers, there exist two coordinate systems: // 1. The global (flat) coordinate system. The chip's center of coordinate system is // defined at the the mid-point of the detector. - // 2. The detector coordinate system. Defined by the row and column segmentation + // 2. The detector coordinate system. Defined by the row and column segmentation. // TODO: add segmentation for VD disks public: @@ -88,7 +88,7 @@ class SegmentationChip /// \param int disk Disk number (0 to 5 for VD) static bool localToDetector(float xRow, float zCol, int& iRow, int& iCol, int subDetID, int layer, int disk) noexcept { - if (!isValidGlob(xRow, zCol, subDetID, layer)) { + if (!isValidLoc(xRow, zCol, subDetID, layer)) { LOGP(debug, "Local coordinates not valid: row = {} cm, col = {} cm", xRow, zCol); return false; } @@ -116,16 +116,11 @@ class SegmentationChip maxWidth = constants::VD::petal::layer::width[layer]; maxLength = constants::VD::petal::layer::length; // TODO: change this to use the layer and disk - } else if (subDetID == 1 && layer <= 3) { // ML + } else if (subDetID == 1) { pitchRow = PitchRowMLOT; pitchCol = PitchColMLOT; - maxWidth = constants::ML::width; - maxLength = constants::ML::length; - } else if (subDetID == 1 && layer >= 4) { // OT - pitchRow = PitchRowMLOT; - pitchCol = PitchColMLOT; - maxWidth = constants::OT::width; - maxLength = constants::OT::length; + maxWidth = constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut; + maxLength = constants::moduleMLOT::chip::length; } // convert to row/col iRow = static_cast(std::floor((maxWidth / 2 - xRow) / pitchRow)); @@ -133,25 +128,22 @@ class SegmentationChip }; // Check local coordinates (cm) validity. - static constexpr bool isValidGlob(float x, float z, int subDetID, int layer) noexcept + static constexpr bool isValidLoc(float x, float z, int subDetID, int layer) noexcept { float maxWidth(0), maxLength(0); if (subDetID == 0) { maxWidth = constants::VD::petal::layer::width[layer]; maxLength = constants::VD::petal::layer::length; // TODO: change this to use the layer and disk - } else if (subDetID == 1 && layer <= 3) { // ML - maxWidth = constants::ML::width; - maxLength = constants::ML::length; - } else if (subDetID == 1 && layer >= 4) { // OT - maxWidth = constants::OT::width; - maxLength = constants::OT::length; + } else if (subDetID == 1) { // ML/OT + maxWidth = constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut; + maxLength = constants::moduleMLOT::chip::length; } return (-maxWidth / 2 < x && x < maxWidth / 2 && -maxLength / 2 < z && z < maxLength / 2); } // Check detector coordinates validity. - static constexpr bool isValidDet(float row, float col, int subDetID, int layer) noexcept + static constexpr bool isValidDet(int row, int col, int subDetID, int layer) noexcept { // Check if the row and column are within the valid range int nRows(0), nCols(0); @@ -159,14 +151,11 @@ class SegmentationChip nRows = constants::VD::petal::layer::nRows[layer]; nCols = constants::VD::petal::layer::nCols; // TODO: change this to use the layer and disk - } else if (subDetID == 1 && layer <= 3) { // ML - nRows = constants::ML::nRows; - nCols = constants::ML::nCols; - } else if (subDetID == 1 && layer >= 4) { // OT - nRows = constants::OT::nRows; - nCols = constants::OT::nCols; + } else if (subDetID == 1) { + nRows = constants::moduleMLOT::chip::nRows; + nCols = constants::moduleMLOT::chip::nCols; } - return (row >= 0 && row < static_cast(nRows) && col >= 0 && col < static_cast(nCols)); + return (row >= 0 && row < nRows && col >= 0 && col < nCols); } /// Transformation from Detector cell coordinates to Geant detector centered @@ -191,7 +180,7 @@ class SegmentationChip detectorToLocalUnchecked(iRow, iCol, xRow, zCol, subDetID, layer, disk); LOG(debug) << "Result from detectorToLocalUnchecked: iRow " << iRow << " -> xRow " << xRow << ", iCol " << iCol << " -> zCol " << zCol << " on subDetID, layer, disk: " << subDetID << " " << layer << " " << disk; - if (!isValidGlob(xRow, zCol, subDetID, layer)) { + if (!isValidLoc(xRow, zCol, subDetID, layer)) { LOGP(debug, "Local coordinates not valid: row = {} cm, col = {} cm", xRow, zCol); return false; } @@ -207,12 +196,9 @@ class SegmentationChip if (subDetID == 0) { xRow = 0.5 * (constants::VD::petal::layer::width[layer] - PitchRowVD) - (row * PitchRowVD); zCol = col * PitchColVD + 0.5 * (PitchColVD - constants::VD::petal::layer::length); - } else if (subDetID == 1 && layer <= 3) { // ML - xRow = 0.5 * (constants::ML::width - PitchRowMLOT) - (row * PitchRowMLOT); - zCol = col * PitchRowMLOT + 0.5 * (PitchRowMLOT - constants::ML::length); - } else if (subDetID == 1 && layer >= 4) { // OT - xRow = 0.5 * (constants::OT::width - PitchRowMLOT) - (row * PitchRowMLOT); - zCol = col * PitchColMLOT + 0.5 * (PitchColMLOT - constants::OT::length); + } else if (subDetID == 1) { // ML/OT + xRow = 0.5 * (constants::moduleMLOT::chip::width - constants::moduleMLOT::chip::passiveEdgeReadOut - PitchRowMLOT) - (row * PitchRowMLOT); + zCol = col * PitchColMLOT + 0.5 * (PitchColMLOT - constants::moduleMLOT::chip::length); } } @@ -263,17 +249,18 @@ class SegmentationChip } /// Print segmentation info - static const void Print() noexcept + static void Print() noexcept { LOG(info) << "Number of rows:\nVD L0: " << constants::VD::petal::layer::nRows[0] << "\nVD L1: " << constants::VD::petal::layer::nRows[1] << "\nVD L2: " << constants::VD::petal::layer::nRows[2] - << "\nML stave: " << constants::ML::nRows - << "\nOT stave: " << constants::OT::nRows; + << "\nML/OT chip: " << constants::moduleMLOT::chip::nRows; LOG(info) << "Number of cols:\nVD: " << constants::VD::petal::layer::nCols - << "\nML stave: " << constants::ML::nCols - << "\nOT stave: " << constants::OT::nCols; + << "\nML/OT chip: " << constants::moduleMLOT::chip::nCols; + + LOG(info) << "Pitch rows x cols [um]:\nVD: " << PitchRowVD * 1e4 << "x" << PitchColVD * 1e4 + << "\nML/OT chip: " << PitchRowMLOT * 1e4 << "x" << PitchColMLOT * 1e4; } }; diff --git a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h index 373e9d972656b..b484e13f3546e 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/Specs.h @@ -16,13 +16,15 @@ #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 // length: dimension in z-axis // width: dimension in xy-axes -// color: for visulisation +// color: for visualisation namespace o2::trk::constants { // Default unit of TGeo = cm @@ -34,11 +36,11 @@ namespace VD // TODO: add a primitive segmentation with more granularity wrt 1/4 { namespace silicon { -constexpr double thickness{30 * mu}; // thickness of the silicon (should be 10 um epitaxial layer + 20 um substrate)? +constexpr double thickness{20 * mu}; // thickness of the silicon (should be 10 um epitaxial layer + 20 um substrate)? } // namespace silicon namespace metalstack { -constexpr double thickness{0 * mu}; // thickness of the copper metal stack - for the moment it is not implemented +constexpr double thickness{80 * mu}; // thickness of the copper metal stack - for the moment it is not implemented. PL: set to 80 um considering silicon as material } // namespace metalstack namespace petal { @@ -49,8 +51,9 @@ namespace layer constexpr double pitchX{10 * mu}; // pitch of the row constexpr double pitchZ{10 * mu}; // pitch of the column constexpr double totalThickness{silicon::thickness + metalstack::thickness}; // total thickness of the chip +constexpr std::array gaps{1.63 * mm, 1.2 * mm, 1.2 * mm}; // gaps between two consecutive petals constexpr std::array radii{0.5 * cm, 1.2 * cm, 2.5 * cm}; // radius of layer in cm -constexpr std::array width{radii[0] * 2 * M_PI / 4, radii[1] * 2 * M_PI / 4, radii[2] * 2 * M_PI / 4}; // width of the quarter of layer in cm +constexpr std::array width{radii[0] * 2 * M_PI / 4 - gaps[0], radii[1] * 2 * M_PI / 4 - gaps[1], radii[2] * 2 * M_PI / 4 - gaps[2]}; // width of the quarter of layer in cm constexpr double length{50 * cm}; // length of the layer constexpr int nCols{static_cast(length / pitchZ)}; // number of columns in the chip constexpr std::array nRows{static_cast(width[0] / pitchX), static_cast(width[1] / pitchX), static_cast(width[2] / pitchX)}; // number of rows in the chip. For the moment is different for each layer since a siner segmentation in repetitive units is stil to be implemented @@ -76,21 +79,18 @@ constexpr double thickness{0 * mu}; // thickness of the copper metal stack - for } // namespace metalstack 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 int nRows{static_cast(width / pitchX)}; // number of columns in the chip -constexpr int nCols{static_cast(length / pitchZ)}; // number of rows in the chip -constexpr double totalThickness{silicon::thickness + metalstack::thickness}; // total thickness of the chip -/// Set to 0 for the moment, to be adjusted with the actual design of the chip if needed -static constexpr float PassiveEdgeReadOut = 0.f; // width of the readout edge (Passive bottom) -static constexpr float PassiveEdgeTop = 0.f; // Passive area on top -static constexpr float PassiveEdgeSide = 0.f; // width of Passive area on left/right of the sensor +constexpr double width{25 * mm}; // width of the chip +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 +constexpr int nCols{static_cast(length / pitchZ)}; // number of columns in the chip } // namespace chip namespace gaps { -constexpr double interChips{0.2 * mm}; // gap between the chips +constexpr double interChips{50 * mu}; // gap between the chips constexpr double outerEdgeLongSide{1 * mm}; // gap between the chips and the outer edges (long side) constexpr double outerEdgeShortSide{0.1 * mm}; // gap between the chips and the outer edges (short side) } // namespace gaps @@ -102,16 +102,27 @@ constexpr int nCols{static_cast(length / chip::pitchZ)}; namespace ML { -constexpr double width{constants::moduleMLOT::width * 1}; // width of the stave -constexpr double length{constants::moduleMLOT::length * 10}; // length of the stave +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 constexpr int nRows{static_cast(width / constants::moduleMLOT::chip::pitchX)}; // number of rows in the stave constexpr int nCols{static_cast(length / constants::moduleMLOT::chip::pitchZ)}; // number of columns in the stave } // namespace ML namespace OT -{ //// TODO: add shorter lenght of the stave of L4 -constexpr double width{moduleMLOT::width * 2}; // width of the stave -constexpr double length{moduleMLOT::length * 20}; // length of the stave +{ +namespace halfstave +{ +constexpr double width{moduleMLOT::width * 1}; // width of the half stave +// constexpr double length{moduleMLOT::length * 20}; // length of the halfstave +constexpr double length{258 * cm}; // length of the halfstave, hardcoded to fit the implemented geometry +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 constexpr int nCols{static_cast(length / moduleMLOT::chip::pitchZ)}; // number of columns in the stave } // namespace OT @@ -124,6 +135,13 @@ constexpr double responseYShift{15.5 * mu}; constexpr double thickness{45 * mu}; } // namespace apts +namespace alice3resp /// parameters for the alice3 chip response +{ +constexpr double pitchX{10.0 * mu}; +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 63c95b1e6b2f6..65194ad6edfcb 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/include/TRKBase/TRKBaseParam.h @@ -19,20 +19,37 @@ namespace o2 { namespace trk { +enum eVDLayout { + kIRIS4 = 0, + kIRISFullCyl, + kIRISFullCyl3InclinedWalls, + kIRIS5, + kIRIS4a, +}; + +enum eMLOTLayout { + kCylindrical = 0, + kSegmented, +}; -enum eLayout { - kCylinder = 0, - kTurboStaves, - kStaggered, +enum eSrvLayout { + kPeacockv1 = 0, + kLOISymm, }; 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 + eSrvLayout layoutSRV = kPeacockv1; // Layout of services - eLayout layoutML = kCylinder; // Type of segmentation for the middle layers - eLayout layoutOL = kCylinder; // Type of segmentation for the outer layers + eVDLayout getLayoutVD() const { return layoutVD; } + eMLOTLayout getLayoutMLOT() const { return layoutMLOT; } + eSrvLayout getLayoutSRV() const { return layoutSRV; } O2ParamDef(TRKBaseParam, "TRKBase"); }; diff --git a/Detectors/FIT/FT0/workflow/src/RawReaderFT0.cxx b/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx similarity index 88% rename from Detectors/FIT/FT0/workflow/src/RawReaderFT0.cxx rename to Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx index b2ef17e540112..572c902fb23f1 100644 --- a/Detectors/FIT/FT0/workflow/src/RawReaderFT0.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/AlmiraParam.cxx @@ -9,5 +9,6 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include "FT0Workflow/RawReaderFT0.h" -using namespace o2::ft0; +#include "TRKBase/AlmiraParam.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 20088179f4dcc..ddfc844cc964d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/GeometryTGeo.cxx @@ -12,6 +12,10 @@ #include #include #include "TRKBase/SegmentationChip.h" +#include "TRKBase/Specs.h" +#include + +#include using Segmentation = o2::trk::SegmentationChip; @@ -23,13 +27,20 @@ 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"; std::string GeometryTGeo::sPetalDiskName = "DISK"; std::string GeometryTGeo::sPetalLayerName = "LAYER"; std::string GeometryTGeo::sStaveName = "TRKStave"; +std::string GeometryTGeo::sHalfStaveName = "TRKHalfStave"; +std::string GeometryTGeo::sModuleName = "TRKModule"; std::string GeometryTGeo::sChipName = "TRKChip"; std::string GeometryTGeo::sSensorName = "TRKSensor"; +std::string GeometryTGeo::sDeadzoneName = "TRKDeadzone"; +std::string GeometryTGeo::sMetalStackName = "TRKMetalStack"; + std::string GeometryTGeo::sWrapperVolumeName = "TRKUWrapVol"; ///< Wrapper volume name, not implemented at the moment o2::trk::GeometryTGeo::~GeometryTGeo() @@ -54,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 @@ -67,26 +78,42 @@ void GeometryTGeo::Build(int loadTrans) LOGP(fatal, "Geometry is not loaded"); } + mLayoutMLOT = o2::trk::TRKBaseParam::Instance().getLayoutMLOT(); + + LOG(debug) << "Overall layout ML and OT: " << mLayoutMLOT; + mNumberOfLayersMLOT = extractNumberOfLayersMLOT(); + mNumberOfPetalsVD = extractNumberOfPetalsVD(); mNumberOfActivePartsVD = extractNumberOfActivePartsVD(); mNumberOfLayersVD = extractNumberOfLayersVD(); - mNumberOfPetalsVD = extractNumberOfPetalsVD(); mNumberOfDisksVD = extractNumberOfDisksVD(); mNumberOfStaves.resize(mNumberOfLayersMLOT); mNumberOfHalfStaves.resize(mNumberOfLayersMLOT); - mLastChipIndex.resize(mNumberOfPetalsVD + mNumberOfLayersMLOT); - mLastChipIndexVD.resize(mNumberOfPetalsVD); - mLastChipIndexMLOT.resize(mNumberOfLayersMLOT); /// ML and OT are part of TRK as the same detector, without disks + mNumberOfModules.resize(mNumberOfLayersMLOT); + mNumberOfChips.resize(mNumberOfLayersMLOT); + mNumberOfChipsPerLayerVD.resize(mNumberOfLayersVD); mNumberOfChipsPerLayerMLOT.resize(mNumberOfLayersMLOT); mNumbersOfChipPerDiskVD.resize(mNumberOfDisksVD); mNumberOfChipsPerPetalVD.resize(mNumberOfPetalsVD); + mLastChipIndex.resize(mNumberOfPetalsVD + mNumberOfLayersMLOT); + mLastChipIndexVD.resize(mNumberOfPetalsVD); + mLastChipIndexMLOT.resize(mNumberOfLayersMLOT); /// ML and OT are part of TRK as the same detector, without disks + for (int i = 0; i < mNumberOfLayersMLOT; i++) { - std::cout << "Layer MLOT: " << i << std::endl; - mNumberOfStaves[i] = extractNumberOfStavesMLOT(i); - mNumberOfHalfStaves[i] = extractNumberOfHalfStavesMLOT(i); + if (mLayoutMLOT == eMLOTLayout::kCylindrical) { + mNumberOfStaves[i] = 1; + mNumberOfHalfStaves[i] = 1; + mNumberOfModules[i] = 1; + mNumberOfChips[i] = 1; + } else { + mNumberOfStaves[i] = extractNumberOfStavesMLOT(i); + mNumberOfHalfStaves[i] = extractNumberOfHalfStavesMLOT(i); + mNumberOfModules[i] = extractNumberOfModulesMLOT(i); + mNumberOfChips[i] = extractNumberOfChipsMLOT(i); + } } int numberOfChipsTotal = 0; @@ -101,13 +128,15 @@ void GeometryTGeo::Build(int loadTrans) /// filling the information for the MLOT for (int i = 0; i < mNumberOfLayersMLOT; i++) { - mNumberOfChipsPerLayerMLOT[i] = extractNumberOfStavesMLOT(i) * extractNumberOfHalfStavesMLOT(i); // for the moment, considering 1 half stave = 1 chip. TODO: add the final segmentation in chips + mNumberOfChipsPerLayerMLOT[i] = mNumberOfStaves[i] * mNumberOfHalfStaves[i] * mNumberOfModules[i] * mNumberOfChips[i]; numberOfChipsTotal += mNumberOfChipsPerLayerMLOT[i]; mLastChipIndex[i + mNumberOfPetalsVD] = numberOfChipsTotal - 1; mLastChipIndexMLOT[i] = numberOfChipsTotal - 1; } - setSize(numberOfChipsTotal); /// temporary, number of chips = number of staves and active parts + setSize(numberOfChipsTotal); + defineMLOTSensors(); + fillTrackingFramesCacheMLOT(); fillMatrixCache(loadTrans); } @@ -130,9 +159,7 @@ int GeometryTGeo::getPetalCase(int index) const int subDetID = getSubDetID(index); if (subDetID == 1) { return -1; - } - - else if (index <= mLastChipIndexVD[mNumberOfPetalsVD - 1]) { + } else if (index <= mLastChipIndexVD[mNumberOfPetalsVD - 1]) { while (index > mLastChipIndexVD[petalcase]) { petalcase++; } @@ -140,6 +167,22 @@ int GeometryTGeo::getPetalCase(int index) const return petalcase; } +//__________________________________________________________________________ +int GeometryTGeo::getDisk(int index) const +{ + int subDetID = getSubDetID(index); + int petalcase = getPetalCase(index); + + if (subDetID == 0) { /// VD + if (index % mNumberOfChipsPerPetalVD[petalcase] < mNumberOfLayersVD) { + return -1; /// layers + } + return (index % mNumberOfChipsPerPetalVD[petalcase]) - mNumberOfLayersVD; + } + + return -1; /// not found or ML/OT +} + //__________________________________________________________________________ int GeometryTGeo::getLayer(int index) const { @@ -156,11 +199,20 @@ int GeometryTGeo::getLayer(int index) const while (index > mLastChipIndex[lay]) { lay++; } - return lay - mNumberOfPetalsVD; /// numeration of MLOT layesrs starting from 0 + return lay - mNumberOfPetalsVD; /// numeration of MLOT layers starting from 0 } 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); @@ -172,9 +224,23 @@ int GeometryTGeo::getStave(int index) const } else if (subDetID == 1) { /// MLOT int lay = getLayer(index); index -= getFirstChipIndex(lay, petalcase, subDetID); // get the index of the sensing element in the layer - return index / mNumberOfHalfStaves[lay]; + + const int Nhs = mNumberOfHalfStaves[lay]; + const int Nmod = mNumberOfModules[lay]; + const int Nchip = mNumberOfChips[lay]; + + if (Nhs == 2) { + int chipsPerModule = Nchip; + int chipsPerHalfStave = Nmod * chipsPerModule; + int chipsPerStave = Nhs * chipsPerHalfStave; + return index / chipsPerStave; + } else if (Nhs == 1) { + int chipsPerModule = Nchip; + int chipsPerStave = Nmod * chipsPerModule; + return index / chipsPerStave; + } } - return -1; /// not found + return -1; } //__________________________________________________________________________ @@ -183,36 +249,89 @@ int GeometryTGeo::getHalfStave(int index) const int subDetID = getSubDetID(index); int lay = getLayer(index); int petalcase = getPetalCase(index); - int stave = getStave(index); if (subDetID == 0) { /// VD return -1; } else if (subDetID == 1) { /// MLOT int lay = getLayer(index); index -= getFirstChipIndex(lay, petalcase, subDetID); // get the index of the sensing element in the layer - return index % 2; /// 0 = half stave left, 1 = half stave right, as geometry is filled /// TODO: generalize once chips will be in place. Can it be working also with chips? + + const int Nhs = mNumberOfHalfStaves[lay]; + const int Nmod = mNumberOfModules[lay]; + const int Nchip = mNumberOfChips[lay]; + + int chipsPerModule = Nchip; + int chipsPerHalfStave = Nmod * chipsPerModule; + int chipsPerStave = Nhs * chipsPerHalfStave; + + int rem = index % chipsPerStave; + return rem / chipsPerHalfStave; // 0 = left, 1 = right } - return -1; /// not found + return -1; } //__________________________________________________________________________ -int GeometryTGeo::getDisk(int index) const +int GeometryTGeo::getModule(int index) const { int subDetID = getSubDetID(index); + int lay = getLayer(index); int petalcase = getPetalCase(index); if (subDetID == 0) { /// VD - if (index % mNumberOfChipsPerPetalVD[petalcase] < mNumberOfLayersVD) { - return -1; /// layers + return -1; + } else if (subDetID == 1) { /// MLOT + int lay = getLayer(index); + index -= getFirstChipIndex(lay, petalcase, subDetID); // get the index of the sensing element in the layer + + const int Nhs = mNumberOfHalfStaves[lay]; + const int Nmod = mNumberOfModules[lay]; + const int Nchip = mNumberOfChips[lay]; + + if (Nhs == 2) { + int chipsPerModule = Nchip; + int chipsPerHalfStave = Nmod * chipsPerModule; + int rem = index % (Nhs * chipsPerHalfStave); + rem = rem % chipsPerHalfStave; + return rem / chipsPerModule; + } else if (Nhs == 1) { + int chipsPerModule = Nchip; + int rem = index % (Nmod * chipsPerModule); + return rem / chipsPerModule; } - return (index % mNumberOfChipsPerPetalVD[petalcase]) - mNumberOfLayersVD; } + return -1; +} - return -1; /// not found or ML/OT +//__________________________________________________________________________ +int GeometryTGeo::getChip(int index) const +{ + int subDetID = getSubDetID(index); + int lay = getLayer(index); + int petalcase = getPetalCase(index); + + if (subDetID == 0) { /// VD + return -1; + } else if (subDetID == 1) { /// MLOT + int lay = getLayer(index); + index -= getFirstChipIndex(lay, petalcase, subDetID); // get the index of the sensing element in the layer + + const int Nhs = mNumberOfHalfStaves[lay]; + const int Nmod = mNumberOfModules[lay]; + const int Nchip = mNumberOfChips[lay]; + + if (Nhs == 2) { + int chipsPerModule = Nchip; + return index % chipsPerModule; + } else if (Nhs == 1) { + int chipsPerModule = Nchip; + return index % chipsPerModule; + } + } + return -1; } //__________________________________________________________________________ -int GeometryTGeo::getChipIndex(int subDetID, int petalcase, int disk, int lay, int stave, int halfstave) const +unsigned short GeometryTGeo::getChipIndex(int subDetID, int petalcase, int disk, int lay, int stave, int halfstave, int mod, int chip) const { if (subDetID == 0) { // VD if (lay == -1) { // disk @@ -220,41 +339,70 @@ int GeometryTGeo::getChipIndex(int subDetID, int petalcase, int disk, int lay, i } else { // layer return getFirstChipIndex(lay, petalcase, subDetID) + lay; } - } else if (subDetID == 1) { // MLOT - if (mNumberOfHalfStaves[lay] == 2) { // staggered geometry - return getFirstChipIndex(lay, petalcase, subDetID) + stave * mNumberOfHalfStaves[lay] + halfstave; - } else if (mNumberOfHalfStaves[lay] == 1) { // turbo geometry - return getFirstChipIndex(lay, petalcase, subDetID) + stave; + } else if (subDetID == 1) { // MLOT + const int Nhs = mNumberOfHalfStaves[lay]; // 1 or 2 + const int Nmod = mNumberOfModules[lay]; // module per half-stave (per stave if Nhs==1) + const int Nchip = mNumberOfChips[lay]; // chips per module + + if (Nhs == 2) { // staggered geometry: layer -> stave -> halfstave -> mod -> chip + int chipsPerModule = Nchip; + int chipsPerHalfStave = Nmod * chipsPerModule; + int chipsPerStave = Nhs * chipsPerHalfStave; + return getFirstChipIndex(lay, petalcase, subDetID) + stave * chipsPerStave + halfstave * chipsPerHalfStave + mod * chipsPerModule + chip; + } else if (Nhs == 1) { // turbo geometry: layer -> stave -> mod -> chip (no halfstave) + int chipsPerModule = Nchip; + int chipsPerStave = Nmod * chipsPerModule; + return getFirstChipIndex(lay, petalcase, subDetID) + stave * chipsPerStave + mod * chipsPerModule + chip; } } - return -1; // not found + + LOGP(warning, "Chip index not found for subDetID %d, petalcase %d, disk %d, layer %d, stave %d, halfstave %d, module %d, chip %d, returning numeric limit", subDetID, petalcase, disk, lay, stave, halfstave, mod, chip); + return std::numeric_limits::max(); // not found } //__________________________________________________________________________ -int GeometryTGeo::getChipIndex(int subDetID, int volume, int lay, int stave, int halfstave) const +unsigned short GeometryTGeo::getChipIndex(int subDetID, int volume, int lay, int stave, int halfstave, int mod, int chip) const { if (subDetID == 0) { // VD return volume; /// In the current configuration for VD, each volume is the sensor element = chip. // TODO: when the geometry naming scheme will be changed, change this method - } else if (subDetID == 1) { // MLOT - if (mNumberOfHalfStaves[lay] == 2) { // staggered geometry - return getFirstChipIndex(lay, -1, subDetID) + stave * mNumberOfHalfStaves[lay] + halfstave; - } else if (mNumberOfHalfStaves[lay] == 1) { // turbo geometry - return getFirstChipIndex(lay, -1, subDetID) + stave; + } else if (subDetID == 1) { // MLOT + const int Nhs = mNumberOfHalfStaves[lay]; // 1 or 2 + const int Nmod = mNumberOfModules[lay]; // module per half-stave (per stave if Nhs==1) + const int Nchip = mNumberOfChips[lay]; // chips per module + + if (Nhs == 2) { // staggered geometry: layer -> stave -> halfstave -> mod -> chip + int chipsPerModule = Nchip; + int chipsPerHalfStave = Nmod * chipsPerModule; + int chipsPerStave = Nhs * chipsPerHalfStave; + return getFirstChipIndex(lay, -1, subDetID) + stave * chipsPerStave + halfstave * chipsPerHalfStave + mod * chipsPerModule + chip; + } else if (Nhs == 1) { // turbo geometry: layer -> stave -> mod -> chip (no halfstave) + int chipsPerModule = Nchip; + int chipsPerStave = Nmod * chipsPerModule; + return getFirstChipIndex(lay, -1, subDetID) + stave * chipsPerStave + mod * chipsPerModule + chip; } } - return -1; // not found + + LOGP(warning, "Chip index not found for subDetID %d, volume %d, layer %d, stave %d, halfstave %d, module %d, chip %d, returning numeric limit", subDetID, volume, lay, stave, halfstave, mod, chip); + return std::numeric_limits::max(); // not found } //__________________________________________________________________________ -bool GeometryTGeo::getChipID(int index, int& subDetID, int& petalcase, int& disk, int& lay, int& stave, int& halfstave) const +bool GeometryTGeo::getChipID(int index, int& subDetID, int& petalcase, int& disk, int& lay, int& stave, int& halfstave, int& mod, int& chip) const { subDetID = getSubDetID(index); petalcase = getPetalCase(index); disk = getDisk(index); lay = getLayer(index); stave = getStave(index); + if (mNumberOfHalfStaves[lay] == 2) { + halfstave = getHalfStave(index); + } else { + halfstave = 0; // if not staggered geometry, return 0 + } halfstave = getHalfStave(index); + mod = getModule(index); + chip = getChip(index); return kTRUE; } @@ -263,34 +411,40 @@ bool GeometryTGeo::getChipID(int index, int& subDetID, int& petalcase, int& disk TString GeometryTGeo::getMatrixPath(int index) const { - int subDetID, petalcase, disk, layer, stave, halfstave; //// TODO: add chips in a second step - getChipID(index, subDetID, petalcase, disk, layer, stave, halfstave); + int subDetID, petalcase, disk, layer, stave, halfstave, mod, chip; + getChipID(index, subDetID, petalcase, disk, layer, stave, halfstave, mod, chip); - // PrintChipID(index, subDetID, petalcase, disk, layer, stave, halfstave); + // PrintChipID(index, subDetID, petalcase, disk, layer, stave, halfstave, mod, chip); - // TString path = "/cave_1/barrel_1/TRKV_2/TRKLayer0_1/TRKStave0_1/TRKChip0_1/TRKSensor0_1/"; /// dummy path, to be used for tests TString path = Form("/cave_1/barrel_1/%s_2/", GeometryTGeo::getTRKVolPattern()); + // build the path if (subDetID == 0) { // VD if (disk >= 0) { + path += Form("%s_%d_%d/", getTRKPetalAssemblyPattern(), petalcase, petalcase + 1); // PETAL_n path += Form("%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalDiskPattern(), disk); // PETALCASEx_DISKy_1 path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalDiskPattern(), disk, getTRKChipPattern(), disk); // PETALCASEx_DISKy_TRKChipy_1 path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalDiskPattern(), disk, getTRKSensorPattern(), disk); // PETALCASEx_DISKy_TRKSensory_1 } else if (layer >= 0) { - path += Form("%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer); // PETALCASEx_LAYERy_1 - path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer, getTRKStavePattern(), layer); // PETALCASEx_LAYERy_TRKStavey_1 + path += Form("%s_%d_%d/", getTRKPetalAssemblyPattern(), petalcase, petalcase + 1); // PETAL_n + path += Form("%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer); // PETALCASEx_LAYERy_1 + // path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer, getTRKStavePattern(), layer); // PETALCASEx_LAYERy_TRKStavey_1 path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer, getTRKChipPattern(), layer); // PETALCASEx_LAYERy_TRKChipy_1 path += Form("%s%d_%s%d_%s%d_1/", getTRKPetalPattern(), petalcase, getTRKPetalLayerPattern(), layer, getTRKSensorPattern(), layer); // PETALCASEx_LAYERy_TRKSensory_1 } - } else if (subDetID == 1) { // MLOT - path += Form("%s%d_1/", getTRKLayerPattern(), layer); // TRKLayerx_1 - path += Form("%s%d_%d/", getTRKStavePattern(), layer, stave); // TRKStavex_y - if (mNumberOfHalfStaves[layer] == 2) { // staggered geometry - path += Form("%s%d_%d/", getTRKChipPattern(), layer, halfstave); // TRKChipx_0/1 - } else if (mNumberOfHalfStaves[layer] == 1) { // turbo geometry - path += Form("%s%d_1/", getTRKChipPattern(), layer); // TRKChipx_1 + } else if (subDetID == 1) { // MLOT + path += Form("%s%d_1/", getTRKLayerPattern(), layer); // TRKLayerx_1 + if (mLayoutMLOT == eMLOTLayout::kCylindrical) { + path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 + } else { + path += Form("%s%d_%d/", getTRKStavePattern(), layer, stave); // TRKStavex_y + if (mNumberOfHalfStaves[layer] == 2) { // staggered geometry + path += Form("%s%d_%d/", getTRKHalfStavePattern(), layer, halfstave); // TRKHalfStavex_y + } + path += Form("%s%d_%d/", getTRKModulePattern(), layer, mod); // TRKModulx_y + path += Form("%s%d_%d/", getTRKChipPattern(), layer, chip); // TRKChipx_y + path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 } - path += Form("%s%d_1/", getTRKSensorPattern(), layer); // TRKSensorx_1 } return path; } @@ -340,6 +494,32 @@ TGeoHMatrix* GeometryTGeo::extractMatrixSensor(int index) const return &matTmp; } +//__________________________________________________________________________ +void GeometryTGeo::defineMLOTSensors() +{ + for (int i = 0; i < mSize; i++) { + if (getSubDetID(i) == 0) { + continue; + } + sensorsMLOT.push_back(i); + } +} + +//__________________________________________________________________________ +void GeometryTGeo::fillTrackingFramesCacheMLOT() +{ + // fill for every sensor of ML & OT its tracking frame parameters + if (!isTrackingFrameCachedMLOT() && !sensorsMLOT.empty()) { + size_t newSize = sensorsMLOT.size(); + mCacheRefXMLOT.resize(newSize); + mCacheRefAlphaMLOT.resize(newSize); + for (int i = 0; i < newSize; i++) { + int sensorId = sensorsMLOT[i]; + extractSensorXAlphaMLOT(sensorId, mCacheRefXMLOT[i], mCacheRefAlphaMLOT[i]); + } + } +} + //__________________________________________________________________________ void GeometryTGeo::fillMatrixCache(int mask) { @@ -362,29 +542,51 @@ void GeometryTGeo::fillMatrixCache(int mask) } } + // build T2L matrices for ML & OT !! VD is yet to be implemented once its geometry will be more refined + if ((mask & o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L)) && !getCacheT2L().isFilled()) { + LOGP(info, "Loading {} T2L matrices from TGeo for ML & OT", getName()); + if (sensorsMLOT.size()) { + int m_Size = sensorsMLOT.size(); + auto& cacheT2L = getCacheT2L(); + cacheT2L.setSize(m_Size); + for (int i = 0; i < m_Size; i++) { + int sensorID = sensorsMLOT[i]; + TGeoHMatrix& hm = createT2LMatrixMLOT(sensorID); + cacheT2L.setMatrix(Mat3D(hm), i); // here, sensorIDs from 0 to 374, sensorIDs shifted to 36 ! + } + } + } + // TODO: build matrices for the cases T2L, T2G and T2GRot when needed } //__________________________________________________________________________ -const char* GeometryTGeo::composeSymNameLayer(int d, int lr) +#ifdef ENABLE_UPGRADES +const char* GeometryTGeo::composeSymNameLayer(int d, int layer) +{ + return Form("%s/%s%d", composeSymNameTRK(d), getTRKLayerPattern(), layer); +} +#endif + +const char* GeometryTGeo::composeSymNameStave(int d, int layer) { - return Form("%s/%s%d", composeSymNameTRK(d), getTRKLayerPattern(), lr); + return Form("%s/%s%d", composeSymNameLayer(d, layer), getTRKStavePattern(), layer); } -const char* GeometryTGeo::composeSymNameStave(int d, int lr) +const char* GeometryTGeo::composeSymNameModule(int d, int layer) { - return Form("%s/%s%d", composeSymNameLayer(d, lr), getTRKStavePattern(), lr); + return Form("%s/%s%d", composeSymNameStave(d, layer), getTRKModulePattern(), layer); } -const char* GeometryTGeo::composeSymNameChip(int d, int lr) +const char* GeometryTGeo::composeSymNameChip(int d, int layer) { - return Form("%s/%s%d", composeSymNameStave(d, lr), getTRKChipPattern(), lr); + return Form("%s/%s%d", composeSymNameStave(d, layer), getTRKChipPattern(), layer); } -const char* GeometryTGeo::composeSymNameSensor(int d, int lr) +const char* GeometryTGeo::composeSymNameSensor(int d, int layer) { - return Form("%s/%s%d", composeSymNameChip(d, lr), getTRKSensorPattern(), lr); + return Form("%s/%s%d", composeSymNameChip(d, layer), getTRKSensorPattern(), layer); } //__________________________________________________________________________ @@ -449,118 +651,261 @@ int GeometryTGeo::extractNumberOfLayersMLOT() } //__________________________________________________________________________ -int GeometryTGeo::extractNumberOfActivePartsVD() const +int GeometryTGeo::extractNumberOfPetalsVD() const { - // The number of active parts returned here is 36 = 4 petals * (3 layers + 6 disks) - int numberOfParts = 0; + int numberOfPetals = 0; + TGeoVolume* trkV = gGeoManager->GetVolume(getTRKVolPattern()); + if (!trkV) { + LOGP(fatal, "{} volume {} is not in the geometry", getName(), getTRKVolPattern()); + return 0; + } - TGeoVolume* vdV = gGeoManager->GetVolume(getTRKVolPattern()); - if (vdV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; + // Loop on all TRKV nodes, count PETAL assemblies and their contents + TObjArray* nodes = trkV->GetNodes(); + if (!nodes) { + LOGP(warning, "{} volume has no child nodes", getTRKVolPattern()); + return 0; } - // Loop on all TRKV nodes, count Layer volumes by checking names - TObjArray* nodes = vdV->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - for (int j = 0; j < nNodes; j++) { - int lrID = -1; - auto nd = dynamic_cast(nodes->At(j)); + LOGP(info, "Searching for petal assemblies in {} (pattern: {})", + getTRKVolPattern(), getTRKPetalAssemblyPattern()); + + for (int j = 0; j < nodes->GetEntriesFast(); j++) { + auto* nd = dynamic_cast(nodes->At(j)); const char* name = nd->GetName(); - if (strstr(name, getTRKPetalPattern()) != nullptr && (strstr(name, getTRKPetalLayerPattern()) != nullptr || strstr(name, getTRKPetalDiskPattern()) != nullptr)) { - numberOfParts++; - if ((lrID = extractVolumeCopy(name, GeometryTGeo::getTRKPetalPattern())) < 0) { - LOG(fatal) << "Failed to extract layer ID from the " << name; + if (strstr(name, getTRKPetalAssemblyPattern()) != nullptr) { + numberOfPetals++; + LOGP(info, "Found petal assembly: {}", name); + + // Get petal volume and its nodes for debugging + TGeoVolume* petalVol = nd->GetVolume(); + if (petalVol) { + TObjArray* petalNodes = petalVol->GetNodes(); + if (petalNodes) { + LOGP(debug, "Petal {} contains {} child nodes", name, petalNodes->GetEntriesFast()); + // Print all nodes in this petal + for (int k = 0; k < petalNodes->GetEntriesFast(); k++) { + auto* petalNode = dynamic_cast(petalNodes->At(k)); + LOGP(debug, " Node {}: {}", k, petalNode->GetName()); + } + } else { + LOGP(warning, "Petal {} has no child nodes", name); + } + } else { + LOGP(warning, "Petal {} has no volume", name); } } } - return numberOfParts; + + if (numberOfPetals == 0) { + LOGP(warning, "No petal assemblies found in geometry"); + } else { + LOGP(info, "Found {} petal assemblies", numberOfPetals); + } + + return numberOfPetals; } //__________________________________________________________________________ -int GeometryTGeo::extractNumberOfDisksVD() const +int GeometryTGeo::extractNumberOfActivePartsVD() const { - // The number of disks returned here is 6 - int numberOfDisks = 0; - + // The number of active parts returned here is 36 = 4 petals * (3 layers + 6 disks) + int numberOfParts = 0; TGeoVolume* vdV = gGeoManager->GetVolume(getTRKVolPattern()); - if (vdV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; + if (!vdV) { + LOGP(fatal, "{} volume {} is not in the geometry", getName(), getTRKVolPattern()); + return 0; } - // Loop on all TRKV nodes, count Layer volumes by checking names + // Find first petal to count its active parts TObjArray* nodes = vdV->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - for (int j = 0; j < nNodes; j++) { - int lrID = -1; - auto nd = dynamic_cast(nodes->At(j)); + if (!nodes) { + LOGP(warning, "{} volume has no child nodes", getTRKVolPattern()); + return 0; + } + + bool petalFound = false; + + for (int j = 0; j < nodes->GetEntriesFast(); j++) { + auto* nd = dynamic_cast(nodes->At(j)); const char* name = nd->GetName(); + if (strstr(name, getTRKPetalAssemblyPattern()) == nullptr) { + continue; + } - if (strstr(name, Form("%s%s", getTRKPetalPattern(), "0")) != nullptr && (strstr(name, getTRKPetalDiskPattern()) != nullptr)) { - numberOfDisks++; - if ((lrID = extractVolumeCopy(name, GeometryTGeo::getTRKPetalPattern())) < 0) { - LOG(fatal) << "Failed to extract layer ID from the " << name; + petalFound = true; + LOGP(info, "Counting active parts in petal: {}", name); + + // Found a petal, count its layers and disks + TGeoVolume* petalVol = nd->GetVolume(); + if (!petalVol) { + LOGP(warning, "Petal {} has no volume", name); + break; + } + + TObjArray* petalNodes = petalVol->GetNodes(); + if (!petalNodes) { + LOGP(warning, "Petal {} has no child nodes", name); + break; + } + + for (int k = 0; k < petalNodes->GetEntriesFast(); k++) { + auto* petalNode = dynamic_cast(petalNodes->At(k)); + const char* nodeName = petalNode->GetName(); + + if (strstr(nodeName, getTRKPetalLayerPattern()) != nullptr || + strstr(nodeName, getTRKPetalDiskPattern()) != nullptr) { + numberOfParts++; + LOGP(debug, "Found active part in {}: {}", name, nodeName); } } + // We only need to check one petal as they're identical + break; } - return numberOfDisks; + + if (!petalFound) { + LOGP(warning, "No petal assembly found matching pattern '{}'", getTRKPetalAssemblyPattern()); + return 0; + } + + if (numberOfParts == 0) { + LOGP(warning, "No active parts (layers/disks) found in petal"); + return 0; + } + + // Multiply by number of petals since all petals are identical + int totalParts = numberOfParts * mNumberOfPetalsVD; + LOGP(info, "Total number of active parts: {} ({}*{})", + totalParts, numberOfParts, mNumberOfPetalsVD); + return totalParts; } //__________________________________________________________________________ -int GeometryTGeo::extractNumberOfPetalsVD() const +int GeometryTGeo::extractNumberOfDisksVD() const { - // The number of petals returned here is 4 = number of petals - int numberOfChips = 0; - + // Count disks in the first petal (all petals are identical) + int numberOfDisks = 0; TGeoVolume* vdV = gGeoManager->GetVolume(getTRKVolPattern()); - if (vdV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; + if (!vdV) { + LOGP(fatal, "{} volume {} is not in the geometry", getName(), getTRKVolPattern()); + return 0; } - // Loop on all TRKV nodes, count Layer volumes by checking names + // Find first petal TObjArray* nodes = vdV->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - for (int j = 0; j < nNodes; j++) { - int lrID = -1; - auto nd = dynamic_cast(nodes->At(j)); - const char* name = nd->GetName(); + if (!nodes) { + LOGP(warning, "{} volume has no child nodes", getTRKVolPattern()); + return 0; + } - if (strstr(name, getTRKPetalPattern()) != nullptr && (strstr(name, Form("%s%s", getTRKPetalLayerPattern(), "0")) != nullptr)) { - numberOfChips++; - if ((lrID = extractVolumeCopy(name, GeometryTGeo::getTRKPetalPattern())) < 0) { - LOG(fatal) << "Failed to extract layer ID from the " << name; + bool petalFound = false; + + for (int j = 0; j < nodes->GetEntriesFast(); j++) { + auto* nd = dynamic_cast(nodes->At(j)); + if (strstr(nd->GetName(), getTRKPetalAssemblyPattern()) == nullptr) { + continue; + } + + petalFound = true; + LOGP(info, "Counting disks in petal: {}", nd->GetName()); + + // Count disks in this petal + TGeoVolume* petalVol = nd->GetVolume(); + if (!petalVol) { + LOGP(warning, "Petal {} has no volume", nd->GetName()); + break; + } + + TObjArray* petalNodes = petalVol->GetNodes(); + if (!petalNodes) { + LOGP(warning, "Petal {} has no child nodes", nd->GetName()); + break; + } + + for (int k = 0; k < petalNodes->GetEntriesFast(); k++) { + auto* petalNode = dynamic_cast(petalNodes->At(k)); + if (strstr(petalNode->GetName(), getTRKPetalDiskPattern()) != nullptr) { + numberOfDisks++; + LOGP(info, "Found disk in {} : {}", nd->GetName(), petalNode->GetName()); } } + // One petal is enough + break; } - return numberOfChips; + + if (!petalFound) { + LOGP(warning, "No petal assembly found matching pattern '{}'", getTRKPetalAssemblyPattern()); + } + + if (numberOfDisks == 0) { + LOGP(warning, "No disks found in VD geometry"); + } + + return numberOfDisks; } //__________________________________________________________________________ int GeometryTGeo::extractNumberOfLayersVD() const { - // The number of layers returned here is 3 + // Count layers in the first petal (all petals are identical) int numberOfLayers = 0; - TGeoVolume* vdV = gGeoManager->GetVolume(getTRKVolPattern()); - if (vdV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; + if (!vdV) { + LOGP(fatal, "{} volume {} is not in the geometry", getName(), getTRKVolPattern()); + return 0; } - // Loop on all TRKV nodes, count Layer volumes by checking names + // Find first petal TObjArray* nodes = vdV->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - for (int j = 0; j < nNodes; j++) { - int lrID = -1; - auto nd = dynamic_cast(nodes->At(j)); - const char* name = nd->GetName(); + if (!nodes) { + LOGP(warning, "{} volume has no child nodes", getTRKVolPattern()); + return 0; + } - if (strstr(name, Form("%s%s", getTRKPetalPattern(), "0")) != nullptr && strstr(name, getTRKPetalLayerPattern()) != nullptr) { - numberOfLayers++; - if ((lrID = extractVolumeCopy(name, GeometryTGeo::getTRKPetalPattern())) < 0) { - LOG(fatal) << "Failed to extract layer ID from the " << name; + bool petalFound = false; + + for (int j = 0; j < nodes->GetEntriesFast(); j++) { + auto* nd = dynamic_cast(nodes->At(j)); + if (strstr(nd->GetName(), getTRKPetalAssemblyPattern()) == nullptr) { + continue; + } + + petalFound = true; + LOGP(info, "Counting layers in petal: {}", nd->GetName()); + + // Count layers in this petal + TGeoVolume* petalVol = nd->GetVolume(); + if (!petalVol) { + LOGP(warning, "Petal {} has no volume", nd->GetName()); + break; + } + + TObjArray* petalNodes = petalVol->GetNodes(); + if (!petalNodes) { + LOGP(warning, "Petal {} has no child nodes", nd->GetName()); + break; + } + + for (int k = 0; k < petalNodes->GetEntriesFast(); k++) { + auto* petalNode = dynamic_cast(petalNodes->At(k)); + if (strstr(petalNode->GetName(), getTRKPetalLayerPattern()) != nullptr) { + numberOfLayers++; + LOGP(info, "Found layer in {} : {}", nd->GetName(), petalNode->GetName()); } } + // One petal is enough + break; + } + + if (!petalFound) { + LOGP(warning, "No petal assembly found matching pattern '{}'", getTRKPetalAssemblyPattern()); + } + + if (numberOfLayers == 0) { + LOGP(warning, "No layers found in VD geometry"); } + return numberOfLayers; } @@ -569,27 +914,82 @@ int GeometryTGeo::extractNumberOfChipsPerPetalVD() const { // The number of chips per petal returned here is 9 for each layer = number of layers + number of quarters of disks per petal int numberOfChips = 0; - TGeoVolume* vdV = gGeoManager->GetVolume(getTRKVolPattern()); - if (vdV == nullptr) { - LOG(fatal) << getName() << " volume " << getTRKVolPattern() << " is not in the geometry"; + if (!vdV) { + LOGP(fatal, "{} volume {} is not in the geometry", getName(), getTRKVolPattern()); + return 0; } - // Loop on all TRKV nodes, count Layer volumes by checking names + // Find first petal assembly TObjArray* nodes = vdV->GetNodes(); - int nNodes = nodes->GetEntriesFast(); - for (int j = 0; j < nNodes; j++) { - int lrID = -1; - auto nd = dynamic_cast(nodes->At(j)); + if (!nodes) { + LOGP(warning, "{} volume has no child nodes", getTRKVolPattern()); + return 0; + } + + bool petalFound = false; + + for (int j = 0; j < nodes->GetEntriesFast(); j++) { + auto* nd = dynamic_cast(nodes->At(j)); const char* name = nd->GetName(); + if (strstr(name, getTRKPetalAssemblyPattern()) == nullptr) { + continue; + } - if (strstr(name, Form("%s%s", getTRKPetalPattern(), "0")) != nullptr && (strstr(name, getTRKPetalLayerPattern()) != nullptr || strstr(name, getTRKPetalDiskPattern()) != nullptr)) { - numberOfChips++; - if ((lrID = extractVolumeCopy(name, GeometryTGeo::getTRKPetalPattern())) < 0) { - LOG(fatal) << "Failed to extract layer ID from the " << name; + petalFound = true; + LOGP(info, "Counting chips in petal: {}", name); + + // Found a petal, count sensors in its layers and disks + TGeoVolume* petalVol = nd->GetVolume(); + if (!petalVol) { + LOGP(warning, "Petal {} has no volume", name); + break; + } + + TObjArray* petalNodes = petalVol->GetNodes(); + if (!petalNodes) { + LOGP(warning, "Petal {} has no child nodes", name); + break; + } + + for (int k = 0; k < petalNodes->GetEntriesFast(); k++) { + auto* petalNode = dynamic_cast(petalNodes->At(k)); + const char* nodeName = petalNode->GetName(); + TGeoVolume* vol = petalNode->GetVolume(); + + if (!vol) { + LOGP(debug, "Node {} has no volume", nodeName); + continue; + } + + // Look for sensors in this volume + TObjArray* subNodes = vol->GetNodes(); + if (!subNodes) { + LOGP(debug, "Node {} has no sub-nodes", nodeName); + continue; + } + + for (int i = 0; i < subNodes->GetEntriesFast(); i++) { + auto* subNode = dynamic_cast(subNodes->At(i)); + if (strstr(subNode->GetName(), getTRKChipPattern()) != nullptr) { + numberOfChips++; + LOGP(debug, "Found chip in {}: {}", nodeName, subNode->GetName()); + } } } + // We only need one petal + break; } + + if (!petalFound) { + LOGP(warning, "No petal assembly found matching pattern '{}'", getTRKPetalAssemblyPattern()); + } + + if (numberOfChips == 0) { + LOGP(warning, "No chips/sensors found in VD petal"); + } + + LOGP(info, "Number of chips per petal: {}", numberOfChips); return numberOfChips; } @@ -643,15 +1043,71 @@ int GeometryTGeo::extractNumberOfHalfStavesMLOT(int lay) const for (int j = 0; j < nNodes; j++) { auto nd = dynamic_cast(nodes->At(j)); /// layer node const char* name = nd->GetName(); - if (strstr(name, getTRKChipPattern()) != nullptr) { + if (strstr(name, getTRKHalfStavePattern()) != nullptr) { numberOfHalfStaves++; } } + + if (numberOfHalfStaves == 0) { + numberOfHalfStaves = 1; /// in case of turbo geometry, there is no half stave volume, but only stave volume + } return numberOfHalfStaves; } //__________________________________________________________________________ -void GeometryTGeo::PrintChipID(int index, int subDetID, int petalcase, int disk, int lay, int stave, int halfstave) const +int GeometryTGeo::extractNumberOfModulesMLOT(int lay) const +{ + int numberOfModules = 0; + + std::string staveName = Form("%s%d", (mNumberOfHalfStaves[lay] == 2 ? getTRKHalfStavePattern() : getTRKStavePattern()), lay); + TGeoVolume* staveV = gGeoManager->GetVolume(staveName.c_str()); + + if (staveV == nullptr) { + LOG(fatal) << getName() << " volume " << (mNumberOfHalfStaves[lay] == 2 ? getTRKHalfStavePattern() : getTRKStavePattern()) << " is not in the geometry"; + } + + // Loop on all staveV nodes, count Module volumes by checking names + TObjArray* nodes = staveV->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); /// stave node + const char* name = nd->GetName(); + if (strstr(name, getTRKModulePattern()) != nullptr) { + numberOfModules++; + } + } + return numberOfModules; +} + +//__________________________________________________________________________ +int GeometryTGeo::extractNumberOfChipsMLOT(int lay) const +{ + int numberOfChips = 0; + + std::string moduleName = Form("%s%d", getTRKModulePattern(), lay); + TGeoVolume* moduleV = gGeoManager->GetVolume(moduleName.c_str()); + + if (moduleV == nullptr) { + LOG(fatal) << getName() << " volume " << getTRKModulePattern() << " is not in the geometry"; + } + + // Loop on all moduleV nodes, count Chip volumes by checking names + TObjArray* nodes = moduleV->GetNodes(); + int nNodes = nodes->GetEntriesFast(); + + for (int j = 0; j < nNodes; j++) { + auto nd = dynamic_cast(nodes->At(j)); /// module node + const char* name = nd->GetName(); + if (strstr(name, getTRKChipPattern()) != nullptr) { + numberOfChips++; + } + } + return numberOfChips; +} + +//__________________________________________________________________________ +void GeometryTGeo::PrintChipID(int index, int subDetID, int petalcase, int disk, int lay, int stave, int halfstave, int mod, int chip) const { std::cout << "\nindex = " << index << std::endl; std::cout << "subDetID = " << subDetID << std::endl; @@ -661,6 +1117,8 @@ void GeometryTGeo::PrintChipID(int index, int subDetID, int petalcase, int disk, std::cout << "first chip index = " << getFirstChipIndex(lay, petalcase, subDetID) << std::endl; std::cout << "stave = " << stave << std::endl; std::cout << "halfstave = " << halfstave << std::endl; + std::cout << "module = " << mod << std::endl; + std::cout << "chip = " << chip << std::endl; } //__________________________________________________________________________ @@ -673,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); @@ -685,9 +1143,21 @@ 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: "); + for (int i = 0; i < mNumberOfLayersMLOT; i++) { + LOGF(info, "%d", mNumberOfModules[i]); + } + LOGF(info, "Number of chips per module MLOT: "); + for (int i = 0; i < mNumberOfLayersMLOT; i++) { + LOGF(info, "%d", mNumberOfChips[i]); + } + LOGF(info, "Number of chips per layer MLOT: "); + for (int i = 0; i < mNumberOfLayersMLOT; i++) { + LOGF(info, "%d", mNumberOfChipsPerLayerMLOT[i]); + } LOGF(info, "Total number of chips: %d", getNumberOfChips()); std::cout << "mLastChipIndex = ["; @@ -708,5 +1178,87 @@ void GeometryTGeo::Print(Option_t*) const std::cout << "]" << std::endl; } +//__________________________________________________________________________ +int GeometryTGeo::getBarrelLayer(int chipID) const +{ + // for barrel layers only, + // so it would be consistent with number of layers i.e. from 0 to 10, + // starting from VD0 to OT10; + // skip the disks; + + int subDetID = getSubDetID(chipID); + int subLayerID = getLayer(chipID); + + if (subDetID < 0 || subDetID > 1) { + LOG(error) << "getBarrelLayer(): Invalid subDetID for barrel: " << subDetID + << ". Expected values are 0 or 1."; + return -1; + } + + if (subLayerID < 0 || subLayerID > 7) { + LOG(error) << "getBarrelLayer(): Invalid subLayerID for barrel: " << subDetID + << ". Expected values are between 0 and 7."; + return -1; + } + + const int baseOffsets[] = {0, 3}; + + return baseOffsets[subDetID] + subLayerID; +} + +//__________________________________________________________________________ +void GeometryTGeo::extractSensorXAlphaMLOT(int chipID, float& x, float& alp) +{ + // works for ML and OT only, a.k.a flat sensors !!! + double locA[3] = {-100., 0., 0.}, locB[3] = {100., 0., 0.}, gloA[3], gloB[3]; + double xp{0}, yp{0}; + + if (getSubDetID(chipID) == 0) { + + LOG(error) << "extractSensorXAlphaMLOT(): VD layers are not supported yet! chipID = " << chipID; + return; + + } else { // flat sensors, ML and OT + const TGeoHMatrix* matL2G = extractMatrixSensor(chipID); + matL2G->LocalToMaster(locA, gloA); + matL2G->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); + xp = gloB[0] - dx * t; + yp = gloB[1] - dy * t; + } + + alp = std::atan2(yp, xp); + x = std::hypot(xp, yp); + o2::math_utils::bringTo02Pi(alp); + + /// TODO: + // once the VD segmentation is done, VD should be added +} + +//__________________________________________________________________________ +TGeoHMatrix& GeometryTGeo::createT2LMatrixMLOT(int chipID) +{ + // works only for ML & OT + // for VD is yet to be implemented once we have more refined geometry + if (getSubDetID(chipID) == 0) { + + LOG(error) << "createT2LMatrixMLOT(): VD layers are not supported yet! chipID = " << chipID + << "returning dummy values! "; + static TGeoHMatrix dummy; + return dummy; + + } else { + static TGeoHMatrix t2l; + t2l.Clear(); + float alpha = getSensorRefAlphaMLOT(chipID); + t2l.RotateZ(alpha * TMath::RadToDeg()); + const TGeoHMatrix* matL2G = extractMatrixSensor(chipID); + const TGeoHMatrix& matL2Gi = matL2G->Inverse(); + t2l.MultiplyLeft(&matL2Gi); + return t2l; + } +} + } // namespace trk } // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h b/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h index f29dcd302537d..e36955cdd150d 100644 --- a/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRK/base/src/TRKBaseLinkDef.h @@ -15,8 +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::conf::ConfigurableParamHelper < 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/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt new file mode 100644 index 0000000000000..9a2194afd3999 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/CMakeLists.txt @@ -0,0 +1,12 @@ +# 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(test) diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt new file mode 100644 index 0000000000000..cdae7c9c379fd --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CMakeLists.txt @@ -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. + + +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 + O2::TRKSimulation + O2::MathUtils + O2::SimulationDataFormat + O2::DetectorsBase + O2::Steer + LABELS trk COMPILE_ONLY) + +o2_add_test_root_macro(CheckTracksCA.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsITS + O2::ITStracking + O2::SimulationDataFormat + O2::DetectorsBase + O2::TRKBase + O2::TRKSimulation + O2::Steer + LABELS trk COMPILE_ONLY) + +o2_add_test_root_macro(CheckClusters.C + PUBLIC_LINK_LIBRARIES O2::DataFormatsTRK + O2::SimulationDataFormat + O2::Framework + 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 new file mode 100644 index 0000000000000..7b9365dbe2011 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckClusters.C @@ -0,0 +1,525 @@ +// 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 CheckClusters.C +/// \brief Macro to check TRK clusters and compare cluster positions to MC hit positions + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#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" +#include "ITSMFTSimulation/AlpideSimResponse.h" +#include "CCDB/BasicCCDBManager.h" +#include "MathUtils/Cartesian.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "DetectorsBase/GeometryManager.h" +#include "Framework/Logger.h" +#endif + +void CheckClusters(const std::string& clusfile = "o2clus_trk.root", + const std::string& hitfile = "o2sim_HitsTRK.root", + const std::string& inputGeom = "o2sim_geometry.root", + const std::string& ccdbUrl = "http://alice-ccdb.cern.ch", + long ccdbTimestamp = -1, + bool batch = false) +{ + gROOT->SetBatch(batch); + + using HitVec = std::vector; + 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) + // and computes Y-intersection planes with the same formulas from Digitizer::init() + auto& ccdbMgr = o2::ccdb::BasicCCDBManager::instance(); + ccdbMgr.setURL(ccdbUrl); + if (ccdbTimestamp > 0) { + ccdbMgr.setTimestamp(ccdbTimestamp); + } + auto* alpResp = ccdbMgr.get("IT3/Calib/APTSResponse"); + if (!alpResp) { + LOGP(fatal, "Cannot retrieve AlpideSimResponse from CCDB at {}", ccdbUrl); + return; + } + const float depthMax = alpResp->getDepthMax(); + + // ── Y-plane shifts: why VD and ML/OT need different values ──────────────── + // + // The APTS pixel response (AlpideSimResponse) uses an internal Y axis where: + // + // y = depthMax ── beam-entry (top) surface + // y = 0 ── charge-collection plane ← where clusters form + // y < 0 ── substrate (no response) + // + // The digitizer (Digitizer::init()) brings hit Y coordinates into this frame + // by adding a per-sub-detector shift before querying the response: + // + // y_APTS = y_local + shift [Digitizer.cxx ::processHit] + // + // The collection plane (y_APTS = 0) is therefore at y_local = −shift + // in the detector local frame. That is the Y value used here when + // propagating the MC hit segment to a single representative point. + // + // ── VD (vertex detector – curved sensors) ───────────────────────────────── + // After SegmentationChip::curvedToFlat() (convention: yFlat = dist − R): + // outer face (beam-entry): yFlat = +halfThickVD = +10 µm + // inner face (exit): yFlat = −halfThickVD = −10 µm + // The digitizer uses: + // + // mSimRespVDShift = depthMax − halfThickVD + // + // so the collection plane (y_APTS = 0) corresponds to: + // + // yPlaneVD = alice3resp::responseYShift = +5 µm + // + // i.e. 5 µm inside from the outer (entry) face. ✓ + // + // ── ML/OT (middle/outer tracker – flat sensors) ──────────────────────────── + // The local Y origin is at the GEOMETRIC CENTRE of the sensor volume. + // The outer (entry) surface is at y_local = +SiliconThicknessMLOT/2. + // The digitizer uses: + // + // mSimRespMLOTShift = depthMax − SiliconThicknessMLOT / 2 + // + // so the collection plane (y_APTS = 0) is at: + // + // yPlaneMLOT = SiliconThicknessMLOT/2 − depthMax + // + // ────────────────────────────────────────────────────────────────────────── + const float halfThicknessMLOT = o2::trk::SegmentationChip::SiliconThicknessMLOT / 2.f; + const float yPlaneVD = (float)o2::trk::constants::alice3resp::responseYShift; // VD: collection plane 5 µm inside outer (entry) face in flat local frame + const float yPlaneMLOT = halfThicknessMLOT - depthMax; // MLOT: entry @ +halfThick, collection depthMax below entry + LOGP(info, "Response depthMax = {:.4f} cm | VD Y-plane = {:.4f} cm | ML/OT Y-plane = {:.4f} cm", + depthMax, yPlaneVD, yPlaneMLOT); + + // ── Geometry ─────────────────────────────────────────────────────────────── + o2::base::GeometryManager::loadGeometry(inputGeom); + auto gman = o2::trk::GeometryTGeo::Instance(); + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + + // ── Hits ─────────────────────────────────────────────────────────────────── + TFile fileH(hitfile.data()); + auto* hitTree = dynamic_cast(fileH.Get("o2sim")); + if (!hitTree) { + 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()); + hitVecPool.resize(hitTree->GetEntries(), nullptr); + + // ── Clusters ─────────────────────────────────────────────────────────────── + TFile fileC(clusfile.data()); + auto* clusTree = dynamic_cast(fileC.Get("o2sim")); + if (!clusTree) { + LOGP(error, "Cannot find o2sim tree in {}", clusfile); + return; + } + + // 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; + } + + // Read entry and accumulate all layers + if (clusTree->GetEntry(0) <= 0) { + LOGP(error, "Cannot read entry 0 from {}", clusfile); + return; + } + + 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; + } + 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; + } + 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); + } + } + } + } + + // ── Output ───────────────────────────────────────────────────────────────── + TFile fout("CheckClusters.root", "recreate"); + // 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, + // 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}, nInvalidEvent{0}, nNoMCHit{0}, nValid{0}; + + // ── Main loop ────────────────────────────────────────────────────────────── + for (unsigned int irof = 0; irof < nROFRec; irof++) { + // Process each layer + for (int iLayer = 0; iLayer < nLayers; iLayer++) { + if (!layerActive[iLayer]) { + continue; + } + 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}; + } + 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; + } + + // ── 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; + } + + // ── 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 = { + (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()); + } + } + } + + // ── Summary ──────────────────────────────────────────────────────────────── + LOGP(info, "=== TRK Cluster vs Hit Summary ==="); + 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); + canvGlobal->Divide(2, 1); + canvGlobal->cd(1); + nt.Draw("clusGlobY:clusGlobX>>h_yx(500,-50,50,500,-50,50)", "", "colz"); + canvGlobal->cd(2); + nt.Draw("clusGlobY:clusGlobZ>>h_yz(500,-100,100,500,-50,50)", "", "colz"); + canvGlobal->SaveAs("trk_clusters_global.png"); + + auto canvRes = new TCanvas("canvRes", "Residuals (cluster - hit) [cm]", 1600, 1200); + canvRes->Divide(2, 3); + canvRes->cd(1)->SetLogy(); + nt.Draw("clusLocX-hitLocX>>h_dx_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + canvRes->cd(2)->SetLogy(); + nt.Draw("clusLocZ-hitLocZ>>h_dz_VD(200,-0.02,0.02)", "subdet==0&&event>=0"); + canvRes->cd(3)->SetLogy(); + nt.Draw("clusLocX-hitLocX>>h_dx_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + canvRes->cd(4)->SetLogy(); + nt.Draw("clusLocZ-hitLocZ>>h_dz_MLOT(200,-0.02,0.02)", "subdet==1&&event>=0"); + canvRes->cd(5)->SetLogz(); + 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("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("clusLocX-hitLocX:layer>>h_dx_vs_lay(20,0,20,200,-0.02,0.02)", "event>=0", "prof"); + canvResVsLayer->cd(2); + 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(); + nt.Write(); + fout.Close(); + + LOGP(info, "Output saved to CheckClusters.root and PNG files"); +} diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C new file mode 100644 index 0000000000000..400457fc98585 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckDigitsTRK.C @@ -0,0 +1,429 @@ +// 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 CheckDigits.C +/// \brief Simple macro to check TRK digits + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TRKBase/SegmentationChip.h" +#include "TRKBase/GeometryTGeo.h" +#include "DataFormatsITSMFT/Digit.h" +#include "TRKSimulation/Hit.h" +#include "MathUtils/Utils.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/IOMCTruthContainerView.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DetectorsBase/GeometryManager.h" +#include "ITSMFTSimulation/AlpideSimResponse.h" +#include "CCDB/BasicCCDBManager.h" + +#include "DataFormatsITSMFT/ROFRecord.h" + +#endif + +#define ENABLE_UPGRADES + +void addTLines(float pitch) +{ + // Add grid lines at multiples of pitch on the current pad + if (!gPad) + return; + + gPad->Update(); + + Double_t xmin = gPad->GetUxmin(); + Double_t xmax = gPad->GetUxmax(); + Double_t ymin = gPad->GetUymin(); + Double_t ymax = gPad->GetUymax(); + + // Calculate the first vertical line position (multiple of pitch) + int nLinesX = 0; + for (float x = xmin; x <= xmax && nLinesX < 1000; x += pitch, nLinesX++) { + TLine* line = new TLine(x, ymin, x, ymax); + line->SetLineStyle(2); + line->SetLineColor(kGray); + line->Draw("same"); + } + + // Calculate the first horizontal line position (multiple of pitch) + int nLinesY = 0; + for (float y = ymin; y <= ymax && nLinesY < 1000; y += pitch, nLinesY++) { + TLine* line = new TLine(xmin, y, xmax, y); + line->SetLineStyle(2); + line->SetLineColor(kGray); + line->Draw("same"); + } + + gPad->Modified(); + gPad->Update(); +} + +void CheckDigits(std::string digifile = "trkdigits.root", std::string hitfile = "o2sim_HitsTRK.root", std::string inputGeom = "o2sim_geometry.root") +{ + gStyle->SetPalette(55); + + using namespace o2::base; + using namespace o2::trk; + + using o2::itsmft::Digit; + using o2::trk::Hit; + + using o2::trk::SegmentationChip; + + TFile* f = TFile::Open("CheckDigits.root", "recreate"); + + TNtuple* nt = new TNtuple("ntd", "digit ntuple", "id:x:y:z:rowD:colD:rowH:colH:xlH:zlH:xlcH:zlcH:dx:dz"); + TNtuple* nt2 = new TNtuple("ntd2", "digit ntuple", "id:z:dxH:dzH"); /// maximum number of elements in a tuple = 15: doing a new tuple to store more variables + + // 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 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"); + int nevH = hitTree->GetEntries(); // hits are stored as one event per entry + std::vector*> hitArray(nevH, nullptr); + + std::vector> mc2hitVec(nevH); + + // Digits — per-layer branches + TFile* digFile = TFile::Open(digifile.data()); + TTree* digTree = (TTree*)digFile->Get("o2sim"); + + int nDigitLayers = 0; + std::vector*> digArr(nTotalLayers, nullptr); + std::vector*> rofRecordsArr(nTotalLayers, nullptr); + std::vector plabelsArr(nTotalLayers, nullptr); + + 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); + + // 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); + } + } + + // 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(); + + o2::dataformats::ConstMCTruthContainer labels; + plabelsArr[iLayer]->copyandflatten(labels); + + // 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(); + + // 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; + + 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); + + 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[iLayer])[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()); + + // 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); + canvXY->Divide(2, 3); + canvXY->cd(1); + nt->Draw("y:x >>h_y_vs_x_VD(1000, -3, 3, 1000, -3, 3)", "id < 12 ", "colz"); + canvXY->cd(2); + nt->Draw("y:z>>h_y_vs_z_VD(1000, -26, 26, 1000, -3, 3)", "id < 12 ", "colz"); + canvXY->cd(3); + nt->Draw("y:x>>h_y_vs_x_ML(1000, -25, 25, 1000, -25, 25)", "id >= 12 && id < 5132 ", "colz"); + canvXY->cd(4); + nt->Draw("y:z>>h_y_vs_z_ML(1000, -70, 70, 1000, -25, 25)", "id >= 12 && id < 5132 ", "colz"); + canvXY->cd(5); + nt->Draw("y:x>>h_y_vs_x_OT(1000, -85, 85, 1000, -85, 85)", "id >= 5132 ", "colz"); + canvXY->cd(6); + nt->Draw("y:z>>h_y_vs_z_OT(1000, -85, 85, 1000, -130, 130)", "id >= 5132 ", "colz"); + canvXY->SaveAs("trkdigits_y_vs_x_vs_z.pdf"); + + // z distributions + auto canvZ = new TCanvas("canvZ", "", 800, 2400); + canvZ->Divide(1, 3); + canvZ->cd(1); + nt->Draw("z>>h_z_VD(500, -26, 26)", "id < 12 "); + canvZ->cd(2); + nt->Draw("z>>h_z_ML(500, -70, 70)", "id >= 12 && id < 5132 "); + canvZ->cd(3); + nt->Draw("z>>h_z_OT(500, -85, 85)", "id >= 5132 "); + canvZ->SaveAs("trkdigits_z.pdf"); + + // dz distributions (difference between local position of digits and hits in x and z) + auto canvdZ = new TCanvas("canvdZ", "", 800, 2400); + canvdZ->Divide(1, 3); + canvdZ->cd(1); + nt->Draw("dz>>h_dz_VD(500, -0.05, 0.05)", "id < 12 "); + canvdZ->cd(2); + nt->Draw("dz>>h_dz_ML(500, -0.05, 0.05)", "id >= 12 && id < 5132 "); + canvdZ->cd(3); + nt->Draw("dz>>h_dz_OT(500, -0.05, 0.05)", "id >= 5132 "); + canvdZ->SaveAs("trkdigits_dz.pdf"); + canvdZ->SaveAs("trkdigits_dz.root"); + + // distributions of differences between local positions of digits and hits in x and z + auto canvdXdZ = new TCanvas("canvdXdZ", "", 1600, 2400); + canvdXdZ->Divide(2, 3); + canvdXdZ->cd(1); + nt->Draw("dx:dz>>h_dx_vs_dz_VD(500, -0.005, 0.005, 500, -0.005, 0.005)", "id < 12", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowVD); + auto h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_VD"); + LOG(info) << "dx, dz"; + Info("VD", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("VD", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->cd(2); + nt->Draw("dx:dz>>h_dx_vs_dz_VD_z(500, -0.005, 0.005, 500, -0.005, 0.005)", "id < 12 && abs(z)<0.5", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowVD); + h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_VD_z"); + Info("VD |z|<1", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("VD |z|<1", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->cd(3); + nt->Draw("dx:dz>>h_dx_vs_dz_ML(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 12 && id < 5132", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_ML"); + Info("ML", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("ML", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->cd(4); + nt->Draw("dx:dz>>h_dx_vs_dz_ML_z(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 12 && id < 5132 && abs(z)<2", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_ML_z"); + Info("ML |z|<2", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("ML |z|<2", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->SaveAs("trkdigits_dx_vs_dz.pdf"); + canvdXdZ->cd(5); + nt->Draw("dx:dz>>h_dx_vs_dz_OT(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 5132", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_OT"); + Info("OT", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("OT", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->cd(6); + nt->Draw("dx:dz>>h_dx_vs_dz_OT_z(600, -0.03, 0.03, 600, -0.03, 0.03)", "id >= 5132 && abs(z)<2", "colz"); + h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_OT_z"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + Info("OT |z|<2", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); + Info("OT |z|<2", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZ->SaveAs("trkdigits_dx_vs_dz.pdf"); + canvdXdZ->SaveAs("trkdigits_dx_vs_dz.root"); + + // distribution of differences between hit start and hit end in local coordinates + auto canvdXdZHit = new TCanvas("canvdXdZHit", "", 1600, 2400); + canvdXdZHit->Divide(2, 3); + canvdXdZHit->cd(1); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_VD(300, -0.03, 0.03, 300, -0.03, 0.03)", "id < 12", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowVD); + LOG(info) << "dxH, dzH"; + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_VD"); + Info("VD", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("VD", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->cd(2); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_VD_z(300, -0.03, 0.03, 300, -0.03, 0.03)", "id < 12 && abs(z)<2", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowVD); + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_VD_z"); + Info("VD |z|<2", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("VD |z|<2", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->cd(3); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_ML(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 12 && id < 5132", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_ML"); + Info("ML", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("ML", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->cd(4); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_ML_z(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 12 && id < 5132 && abs(z)<2", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_ML_z"); + Info("ML |z|<2", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("ML |z|<2", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->SaveAs("trkdigits_dxH_vs_dzH.pdf"); + canvdXdZHit->cd(5); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_OT(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 5132", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_OT"); + Info("OT", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("OT", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->cd(6); + nt2->Draw("dxH:dzH>>h_dxH_vs_dzH_OT_z(300, -0.03, 0.03, 300, -0.03, 0.03)", "id >= 5132 && abs(z)<2", "colz"); + addTLines(o2::trk::SegmentationChip::PitchRowMLOT); + h = (TH2F*)gPad->GetPrimitive("h_dxH_vs_dzH_OT_z"); + Info("OT |z|<2", "RMS(dxH)=%.1f mu", h->GetRMS(2) * 1e4); + Info("OT |z|<2", "RMS(dzH)=%.1f mu", h->GetRMS(1) * 1e4); + canvdXdZHit->SaveAs("trkdigits_dxH_vs_dzH.pdf"); + + f->Write(); + f->Close(); +} diff --git a/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C new file mode 100644 index 0000000000000..f7917ca4203f1 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/CheckTracksCA.C @@ -0,0 +1,648 @@ +// 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 A. Daribayeva +/// Quality assurance test on reconstructed tracks, producing efficiency plots and performance table + +#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 + +#include "DataFormatsITS/TrackITS.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTrack.h" +#include "Steer/MCKinematicsReader.h" +#include "TRKSimulation/Hit.h" +#include "TRKBase/GeometryTGeo.h" +#include "DetectorsBase/GeometryManager.h" + +#endif + +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) + int nHits = 0; ///< Total number of hits + float pt = 0.0f; ///< Particle pT + + void addHit(int layer) + { + if (!layerHits[layer]) { + layerHits[layer] = true; + nHits++; + } + } + + bool hasConsecutiveLayers(int nConsecutive) const + { + for (int startLayer = 0; startLayer <= 11 - nConsecutive; ++startLayer) { + bool allSet = true; + for (int i = 0; i < nConsecutive; ++i) { + if (!layerHits[startLayer + i]) { + allSet = false; + break; + } + } + if (allSet) { + return true; + } + } + return false; + } +}; + +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 outFile = "RecoPerformanceTable.dat", + std::string outFile1 = "RecoTracksQA.root") +{ + + std::cout << "=== Starting TRK Track Reconstruction Quality Assurance ===" << std::endl; + std::cout << "Input files:" << std::endl; + std::cout << " Tracks: " << trackfile << std::endl; + std::cout << " Kinematics: " << kinefile << std::endl; + std::cout << " Hits: " << hitsfile << 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); + + // Open hits file to count hits per particle per layer + TFile* hitsFile = TFile::Open(hitsfile.c_str(), "READ"); + if (!hitsFile || hitsFile->IsZombie()) { + 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; + return; + } + + // Open reconstructed tracks file + TFile* tracFile = TFile::Open(trackfile.c_str(), "READ"); + if (!tracFile || tracFile->IsZombie()) { + 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; + } + + // ============== MC part =============================== + // Analyze hits tree to count hits per particle per layer + std::cout << "Analyzing hits from tree..." << std::endl; + std::unordered_map particleHitMap; + + // Load geometry for layer determination + o2::base::GeometryManager::loadGeometry(); + auto* gman = o2::trk::GeometryTGeo::Instance(); + + std::vector* trkHit = nullptr; + hitsTree->SetBranchAddress("TRKHit", &trkHit); + + Long64_t nHitsEntries = hitsTree->GetEntries(); + std::cout << "Processing " << nHitsEntries << " hit entries..." << std::endl; + + for (Long64_t iEntry = 0; iEntry < nHitsEntries; ++iEntry) { + hitsTree->GetEntry(iEntry); + + for (const auto& hit : *trkHit) { + // Skip disk hits (only barrel) + if (gman->getDisk(hit.GetDetectorID()) != -1) { + continue; + } + + // Determine layer + const int layer = gman->getBarrelLayer(hit.GetDetectorID()); + + // Create label for this particle + o2::MCCompLabel label(hit.GetTrackID(), static_cast(iEntry), 0); + + // Add hit to particle's hit map + particleHitMap[label].addHit(layer); + } + } + + std::cout << "Found " << particleHitMap.size() << " unique particles with hits" << std::endl; + + //=========== need to set the min and max ranges for hists + std::vector pTDist; + std::unordered_map MCTrackMap; + + // counters for general statisics + int counterPrimaries{0}, counterSecondaries{0}; + + 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]; + + // Create label for this particle + o2::MCCompLabel label(iTrack, iEvent, 0); + + auto hitIt = particleHitMap.find(label); + if (hitIt != particleHitMap.end()) { + + if (mcTrack.isPrimary()) { + counterPrimaries++; + } + 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; + } + } + } + + 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}; + + // 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); + + 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 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); + + if (hitIt == particleHitMap.end() || mcIt == MCTrackMap.end()) { + continue; + } + + int nHits = hitIt->second.nHits; + if (nHits < 7 || nHits > 11) { + continue; + } + + bool mcHasN = hitIt->second.hasConsecutiveLayers(nHits); + bool recoHasN = hasConsecutiveLayers(track, nClusters); + + float mcPt = mcIt->second.GetPt(); + float recoPT = track.getPt(); + + // 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()) { + foundAllFake.insert(key); + + if (mcIt->second.isPrimary()) { + foundPrimFake.insert(key); + hPtNumInclusiveFake.Fill(mcPt); + } else if (mcIt->second.isSecondary()) { + foundSecFake.insert(key); + } + } + } + + // 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); + } + } + + // 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; + +} // end of macro + +void setAutoXRange(TH1* h, RangeMode mode, + const TH1* hRef, + double threshold, + int marginBins) +{ + if (!h) + return; + + const TH1* hScan = h; + + if (mode == RangeMode::ReferenceContent) { + if (!hRef) + return; + hScan = hRef; + } + + 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; + } + } + + 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/macros/test/run_test.sh b/Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh new file mode 100644 index 0000000000000..d0953a342af04 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/macros/test/run_test.sh @@ -0,0 +1,10 @@ +#Number of events to simulate +nEvents=10 + +# Simulating +o2-sim-serial-run5 -n $nEvents -g pythia8hi -m TRK --configKeyValues "TRKBase.layoutML=kTurboStaves;TRKBase.layoutOT=kStaggered;">& sim_TRK.log + +# Digitizing +o2-sim-digitizer-workflow -b >& digiTRK.log + +root.exe -b -q CheckDigits.C+ >& CheckDigits.log diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt new file mode 100644 index 0000000000000..45ce53ba7c3a3 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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(TRKReconstruction + TARGETVARNAME targetName + SOURCES src/Clusterer.cxx + $<$:src/ClustererACTS.cxx> + PUBLIC_LINK_LIBRARIES + Microsoft.GSL::GSL + O2::DataFormatsITSMFT + O2::DataFormatsTRK + O2::SimulationDataFormat + O2::TRKBase + nlohmann_json::nlohmann_json + ${actsTarget}) + +if(Acts_FOUND) + target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS) +endif() diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h new file mode 100644 index 0000000000000..3d30eb5068efe --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/Clusterer.h @@ -0,0 +1,186 @@ +// 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 Clusterer.h +/// \brief Definition of the TRK cluster finder + +#ifndef ALICEO2_TRK_CLUSTERER_H +#define ALICEO2_TRK_CLUSTERER_H + +// uncomment to allow diagonal clusters, e.g. |* | +// | *| +#define _ALLOW_DIAGONAL_TRK_CLUSTERS_ + +#include "DataFormatsITSMFT/Digit.h" +#include "DataFormatsITSMFT/ROFRecord.h" +#include "DataFormatsITSMFT/ClusterPattern.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "TRKBase/Specs.h" +#include "MathUtils/Cartesian.h" +#include +#include +#include +#include +#include +#include + +namespace o2::trk +{ + +class GeometryTGeo; + +class Clusterer +{ + public: + static constexpr int MaxLabels = 10; + static constexpr int MaxHugeClusWarn = 5; + + using Digit = o2::itsmft::Digit; + using DigROFRecord = o2::itsmft::ROFRecord; + using DigMC2ROFRecord = o2::itsmft::MC2ROFRecord; + using ClusterTruth = o2::dataformats::MCTruthContainer; + using ConstDigitTruth = o2::dataformats::ConstMCTruthContainerView; + using Label = o2::MCCompLabel; + + //---------------------------------------------- + struct BBox { + uint16_t chipID = 0xffff; + uint16_t rowMin = 0xffff, colMin = 0xffff; + uint16_t rowMax = 0, colMax = 0; + explicit BBox(uint16_t c) : chipID(c) {} + bool isInside(uint16_t r, uint16_t c) const { return r >= rowMin && r <= rowMax && c >= colMin && c <= colMax; } + uint16_t rowSpan() const { return rowMax - rowMin + 1; } + uint16_t colSpan() const { return colMax - colMin + 1; } + bool isAcceptableSize() const + { + return rowSpan() <= o2::itsmft::ClusterPattern::MaxRowSpan && + colSpan() <= o2::itsmft::ClusterPattern::MaxColSpan; + } + void adjust(uint16_t r, uint16_t c) + { + if (r < rowMin) { + rowMin = r; + } + if (r > rowMax) { + rowMax = r; + } + if (c < colMin) { + colMin = c; + } + if (c > colMax) { + colMax = c; + } + } + }; + + //---------------------------------------------- + struct ClustererThread { + Clusterer* parent = nullptr; + // column buffers (pre-cluster state); extra sentinel entries at [0] and [size-1] + int* column1 = nullptr; + int* column2 = nullptr; + int* curr = nullptr; ///< current column pre-cluster indices + int* prev = nullptr; ///< previous column pre-cluster indices + int size = constants::moduleMLOT::chip::nRows + 2; ///< reallocated per chip in initChip + + // pixels[i] = {next_in_chain, global_digit_index} + std::vector> pixels; + std::vector preClusterHeads; + std::vector preClusterIndices; + uint16_t currCol = 0xffff; + bool noLeftCol = true; + + std::array labelsBuff; ///< MC label buffer for one cluster + std::vector> pixArrBuff; ///< (row,col) pixel buffer for pattern + + // per-thread output (accumulated, then merged back by caller) + std::vector clusters; + std::vector patterns; + ClusterTruth labels; + + ///< reset column buffer + void resetColumn(int* buff) const { std::memset(buff, -1, sizeof(int) * (size - 2)); } + ///< swap current and previous column buffers + void swapColumnBuffers() { std::swap(prev, curr); } + + ///< append pixel ip to the pre-cluster headed at preClusterIndex + void expandPreCluster(uint32_t ip, uint16_t row, int preClusterIndex) + { + auto& firstIndex = preClusterHeads[preClusterIndices[preClusterIndex]]; + pixels.emplace_back(firstIndex, ip); + firstIndex = pixels.size() - 1; + curr[row] = preClusterIndex; + } + + ///< start a new pre-cluster with pixel ip at given row + void addNewPreCluster(uint32_t ip, uint16_t row) + { + preClusterHeads.push_back(pixels.size()); + pixels.emplace_back(-1, ip); + int lastIndex = preClusterIndices.size(); + preClusterIndices.push_back(lastIndex); + curr[row] = lastIndex; + } + + void fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled); + void initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom); + void updateChip(gsl::span digits, uint32_t ip); + void finishChip(gsl::span digits, + const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, + GeometryTGeo* geom); + void finishChipSingleHitFast(gsl::span digits, uint32_t hit, + const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, + GeometryTGeo* geom); + void processChip(gsl::span digits, int chipFirst, int chipN, + std::vector* clustersOut, std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, ClusterTruth* labelsClusPtr, + GeometryTGeo* geom); + void streamCluster(const BBox& bbox, const std::vector>& pixbuf, + uint32_t totalCharge, bool doLabels, int nlab, + uint16_t chipID, int subDetID, int layer, int disk); + + ~ClustererThread() + { + delete[] column1; + delete[] column2; + } + explicit ClustererThread(Clusterer* par = nullptr) : parent(par) {} + ClustererThread(const ClustererThread&) = delete; + ClustererThread& operator=(const ClustererThread&) = delete; + }; + //---------------------------------------------- + + virtual void process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels = nullptr, + ClusterTruth* clusterLabels = nullptr, + 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; + std::vector mSortIdx; ///< reusable per-ROF sort buffer +}; + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h new file mode 100644 index 0000000000000..37a148aa78afb --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/include/TRKReconstruction/ClustererACTS.h @@ -0,0 +1,47 @@ +// 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 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 + +#include "TRKReconstruction/Clusterer.h" + +namespace o2::trk +{ + +class GeometryTGeo; + +class ClustererACTS : public Clusterer +{ + public: + void process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels = nullptr, + ClusterTruth* clusterLabels = nullptr, + gsl::span digMC2ROFs = {}, + std::vector* clusterMC2ROFs = nullptr) override; + + private: +}; + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx new file mode 100644 index 0000000000000..d60d6900657ba --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/Clusterer.cxx @@ -0,0 +1,465 @@ +// 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 Clusterer.cxx +/// \brief Implementation of the TRK cluster finder + +#include "TRKReconstruction/Clusterer.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/SegmentationChip.h" + +#include +#include + +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, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels, + ClusterTruth* clusterLabels, + gsl::span digMC2ROFs, + std::vector* clusterMC2ROFs) +{ + if (!mThread) { + mThread = std::make_unique(this); + } + + auto* geom = o2::trk::GeometryTGeo::Instance(); + + for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) { + const auto& inROF = digitROFs[iROF]; + const auto outFirst = static_cast(clusters.size()); + const int first = inROF.getFirstEntry(); + const int nEntries = inROF.getNEntries(); + + if (nEntries == 0) { + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, 0); + continue; + } + + // Sort digit indices within this ROF by (chipID, col, row) so we can process + // chip by chip, column by column -- the same ordering the ALPIDE scanner expects. + mSortIdx.resize(nEntries); + std::iota(mSortIdx.begin(), mSortIdx.end(), first); + std::sort(mSortIdx.begin(), mSortIdx.end(), [&digits](int a, int b) { + const auto& da = digits[a]; + const auto& db = digits[b]; + if (da.getChipIndex() != db.getChipIndex()) { + return da.getChipIndex() < db.getChipIndex(); + } + if (da.getColumn() != db.getColumn()) { + return da.getColumn() < db.getColumn(); + } + return da.getRow() < db.getRow(); + }); + + // Process one chip at a time + int sliceStart = 0; + while (sliceStart < nEntries) { + const int chipFirst = sliceStart; + const uint16_t chipID = digits[mSortIdx[sliceStart]].getChipIndex(); + while (sliceStart < nEntries && digits[mSortIdx[sliceStart]].getChipIndex() == chipID) { + ++sliceStart; + } + const int chipN = sliceStart - chipFirst; + + mThread->processChip(digits, chipFirst, chipN, &clusters, &patterns, digitLabels, clusterLabels, geom); + } + + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), + 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); + } + } +} + +//__________________________________________________ +void Clusterer::ClustererThread::processChip(gsl::span digits, + int chipFirst, int chipN, + std::vector* clustersOut, + std::vector* patternsOut, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) +{ + // chipFirst and chipN are relative to mSortIdx (i.e. mSortIdx[chipFirst..chipFirst+chipN-1] + // are the global digit indices for this chip, already sorted by col then row). + // We use parent->mSortIdx to resolve the global index of each pixel. + const auto& sortIdx = parent->mSortIdx; + + if (chipN == 1) { + finishChipSingleHitFast(digits, sortIdx[chipFirst], labelsDigPtr, labelsClusPtr, geom); + } else { + initChip(digits, sortIdx[chipFirst], geom); + for (int i = chipFirst + 1; i < chipFirst + chipN; ++i) { + updateChip(digits, sortIdx[i]); + } + finishChip(digits, labelsDigPtr, labelsClusPtr, geom); + } + + // Flush per-thread output into the caller's containers + if (!clusters.empty()) { + clustersOut->insert(clustersOut->end(), clusters.begin(), clusters.end()); + clusters.clear(); + } + if (!patterns.empty()) { + patternsOut->insert(patternsOut->end(), patterns.begin(), patterns.end()); + patterns.clear(); + } + if (labelsClusPtr && labels.getNElements()) { + labelsClusPtr->mergeAtBack(labels); + labels.clear(); + } +} + +//__________________________________________________ +void Clusterer::ClustererThread::initChip(gsl::span digits, uint32_t first, GeometryTGeo* geom) +{ + const uint16_t chipID = digits[first].getChipIndex(); + + // Determine the number of rows for this chip's sensor type + size = constants::moduleMLOT::chip::nRows + 2; // default for ML/OT + if (geom) { + if (geom->getSubDetID(chipID) == 0) { // VD + const int layer = geom->getLayer(chipID); + size = constants::VD::petal::layer::nRows[layer] + 2; + } + } + + delete[] column1; + delete[] column2; + column1 = new int[size]; + column2 = new int[size]; + column1[0] = column1[size - 1] = -1; + column2[0] = column2[size - 1] = -1; + prev = column1 + 1; + curr = column2 + 1; + resetColumn(curr); + + pixels.clear(); + preClusterHeads.clear(); + preClusterIndices.clear(); + + const auto& pix = digits[first]; + currCol = pix.getColumn(); + curr[pix.getRow()] = 0; + preClusterHeads.push_back(0); + preClusterIndices.push_back(0); + pixels.emplace_back(-1, first); + noLeftCol = true; +} + +//__________________________________________________ +void Clusterer::ClustererThread::updateChip(gsl::span digits, uint32_t ip) +{ + const auto& pix = digits[ip]; + uint16_t row = pix.getRow(); + + if (currCol != pix.getColumn()) { + swapColumnBuffers(); + resetColumn(curr); + noLeftCol = false; + if (pix.getColumn() > currCol + 1) { + // gap: no connection with previous column + currCol = pix.getColumn(); + addNewPreCluster(ip, row); + noLeftCol = true; + return; + } + currCol = pix.getColumn(); + } + + bool orphan = true; + + if (noLeftCol) { + if (curr[row - 1] >= 0) { + expandPreCluster(ip, row, curr[row - 1]); + return; + } + } else { +#ifdef _ALLOW_DIAGONAL_TRK_CLUSTERS_ + int neighbours[]{curr[row - 1], prev[row], prev[row + 1], prev[row - 1]}; +#else + int neighbours[]{curr[row - 1], prev[row]}; +#endif + for (auto pci : neighbours) { + if (pci < 0) { + continue; + } + if (orphan) { + expandPreCluster(ip, row, pci); + orphan = false; + continue; + } + // merge two pre-clusters: assign the smaller index to both + if (preClusterIndices[pci] < preClusterIndices[curr[row]]) { + preClusterIndices[curr[row]] = preClusterIndices[pci]; + } else { + preClusterIndices[pci] = preClusterIndices[curr[row]]; + } + } + } + if (orphan) { + addNewPreCluster(ip, row); + } +} + +//__________________________________________________ +void Clusterer::ClustererThread::finishChip(gsl::span digits, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) +{ + const uint16_t chipID = digits[pixels[0].second].getChipIndex(); + + for (size_t i1 = 0; i1 < preClusterHeads.size(); ++i1) { + auto ci = preClusterIndices[i1]; + if (ci < 0) { + continue; + } + BBox bbox(chipID); + int nlab = 0; + uint32_t totalCharge = 0; + pixArrBuff.clear(); + + // Walk the linked list for this pre-cluster head + auto collectPixels = [&](int head) { + int next = head; + while (next >= 0) { + const auto& pixEntry = pixels[next]; + const auto& d = digits[pixEntry.second]; + uint16_t r = d.getRow(), c = d.getColumn(); + pixArrBuff.emplace_back(r, c); + bbox.adjust(r, c); + totalCharge += d.getCharge(); + if (labelsClusPtr) { + fetchMCLabels(pixEntry.second, labelsDigPtr, nlab); + } + next = pixEntry.first; + } + }; + + collectPixels(preClusterHeads[i1]); + preClusterIndices[i1] = -1; + + for (size_t i2 = i1 + 1; i2 < preClusterHeads.size(); ++i2) { + if (preClusterIndices[i2] != ci) { + continue; + } + collectPixels(preClusterHeads[i2]); + preClusterIndices[i2] = -1; + } + + // Determine geometry info + int subDetID = -1, layer = -1, disk = -1; + if (geom) { + subDetID = geom->getSubDetID(chipID); + layer = geom->getLayer(chipID); + disk = geom->getDisk(chipID); + } + + const bool doLabels = (labelsClusPtr != nullptr); + if (bbox.isAcceptableSize()) { + streamCluster(bbox, pixArrBuff, totalCharge, doLabels, nlab, chipID, subDetID, layer, disk); + } else { + // Huge cluster: split into MaxRowSpan x MaxColSpan tiles (same as ITS3) + auto warnLeft = MaxHugeClusWarn - parent->mNHugeClus; + if (warnLeft > 0) { + LOGP(warn, "Splitting huge TRK cluster: chipID {}, rows {}:{} cols {}:{}{}", + chipID, bbox.rowMin, bbox.rowMax, bbox.colMin, bbox.colMax, + warnLeft == 1 ? " (further warnings muted)" : ""); + parent->mNHugeClus++; + } + BBox bboxT(chipID); + bboxT.colMin = bbox.colMin; + do { + bboxT.rowMin = bbox.rowMin; + bboxT.colMax = std::min(bbox.colMax, uint16_t(bboxT.colMin + o2::itsmft::ClusterPattern::MaxColSpan - 1)); + do { + bboxT.rowMax = std::min(bbox.rowMax, uint16_t(bboxT.rowMin + o2::itsmft::ClusterPattern::MaxRowSpan - 1)); + std::vector> subPix; + uint32_t subCharge = 0; + for (const auto& [r, c] : pixArrBuff) { + if (bboxT.isInside(r, c)) { + subPix.emplace_back(r, c); + subCharge += 1; + } + } + if (!subPix.empty()) { + streamCluster(bboxT, subPix, subCharge, doLabels, nlab, chipID, subDetID, layer, disk); + } + bboxT.rowMin = bboxT.rowMax + 1; + } while (bboxT.rowMin <= bbox.rowMax); + bboxT.colMin = bboxT.colMax + 1; + } while (bboxT.colMin <= bbox.colMax); + } + } + // flush per-thread output to the caller via processChip +} + +//__________________________________________________ +void Clusterer::ClustererThread::finishChipSingleHitFast(gsl::span digits, uint32_t hit, + const ConstDigitTruth* labelsDigPtr, + ClusterTruth* labelsClusPtr, + GeometryTGeo* geom) +{ + const auto& d = digits[hit]; + const uint16_t chipID = d.getChipIndex(); + const uint16_t row = d.getRow(); + const uint16_t col = d.getColumn(); + + if (labelsClusPtr) { + int nlab = 0; + fetchMCLabels(hit, labelsDigPtr, nlab); + const auto cnt = static_cast(clusters.size()); + for (int i = nlab; i--;) { + labels.addElement(cnt, labelsBuff[i]); + } + } + + // 1×1 pattern: rowSpan=1, colSpan=1, one byte = 0x80 + patterns.emplace_back(1); + patterns.emplace_back(1); + patterns.emplace_back(0x80); + + Cluster cluster; + cluster.chipID = chipID; + cluster.row = row; + cluster.col = col; + cluster.size = 1; + if (geom) { + cluster.subDetID = geom->getSubDetID(chipID); + cluster.layer = geom->getLayer(chipID); + cluster.disk = geom->getDisk(chipID); + } + clusters.emplace_back(cluster); +} + +//__________________________________________________ +void Clusterer::ClustererThread::streamCluster(const BBox& bbox, + const std::vector>& pixbuf, + uint32_t totalCharge, + bool doLabels, int nlab, + uint16_t chipID, int subDetID, int layer, int disk) +{ + if (doLabels) { + const auto cnt = static_cast(clusters.size()); + for (int i = nlab; i--;) { + labels.addElement(cnt, labelsBuff[i]); // accumulate in thread-local buffer + } + } + + const uint16_t rowSpanW = bbox.rowSpan(); + const uint16_t colSpanW = bbox.colSpan(); + + // Encode the pixel pattern bitmap (rowSpan, colSpan, bytes...) + std::array patt{}; + for (const auto& [r, c] : pixbuf) { + uint32_t ir = r - bbox.rowMin, ic = c - bbox.colMin; + int nbit = ir * colSpanW + ic; + patt[nbit >> 3] |= (0x1 << (7 - (nbit % 8))); + } + patterns.emplace_back(static_cast(rowSpanW)); + patterns.emplace_back(static_cast(colSpanW)); + int nBytes = (rowSpanW * colSpanW + 7) / 8; + patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes); + + Cluster cluster; + cluster.chipID = chipID; + cluster.row = bbox.rowMin; + cluster.col = bbox.colMin; + cluster.size = static_cast(pixbuf.size()); + cluster.subDetID = static_cast(subDetID); + cluster.layer = static_cast(layer); + cluster.disk = static_cast(disk); + clusters.emplace_back(cluster); +} + +//__________________________________________________ +void Clusterer::ClustererThread::fetchMCLabels(uint32_t digID, const ConstDigitTruth* labelsDig, int& nfilled) +{ + if (nfilled >= MaxLabels) { + return; + } + if (!labelsDig || digID >= labelsDig->getIndexedSize()) { + return; + } + const auto& lbls = labelsDig->getLabels(digID); + for (int i = lbls.size(); i--;) { + int ic = nfilled; + for (; ic--;) { + if (labelsBuff[ic] == lbls[i]) { + return; // already present + } + } + labelsBuff[nfilled++] = lbls[i]; + if (nfilled >= MaxLabels) { + break; + } + } +} + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx new file mode 100644 index 0000000000000..30ab503b7e250 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/reconstruction/src/ClustererACTS.cxx @@ -0,0 +1,396 @@ +// 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 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" +#include "DataFormatsITSMFT/ClusterPattern.h" +#include + +#include +#include +#include + +using namespace o2::trk; + +// Data formats for ACTS interface +struct Cell2D { + Cell2D(int rowv, int colv, uint32_t digIdx = 0) : row(rowv), col(colv), digitIdx(digIdx) {} + int row, col; + uint32_t digitIdx; ///< Index of the original digit (for MC label retrieval) + Acts::Ccl::Label label{Acts::Ccl::NO_LABEL}; +}; + +int getCellRow(const Cell2D& cell) +{ + return cell.row; +} + +int getCellColumn(const Cell2D& cell) +{ + return cell.col; +} + +bool operator==(const Cell2D& left, const Cell2D& right) +{ + return left.row == right.row && left.col == right.col; +} + +bool cellComp(const Cell2D& left, const Cell2D& right) +{ + return (left.row == right.row) ? left.col < right.col : left.row < right.row; +} + +struct Cluster2D { + std::vector cells; + std::size_t hash{0}; +}; + +void clusterAddCell(Cluster2D& cl, const Cell2D& cell) +{ + cl.cells.push_back(cell); +} + +void hash(Cluster2D& cl) +{ + std::ranges::sort(cl.cells, cellComp); + cl.hash = 0; + // for (const Cell2D& c : cl.cells) { + // boost::hash_combine(cl.hash, c.col); + // } +} + +bool clHashComp(const Cluster2D& left, const Cluster2D& right) +{ + return left.hash < right.hash; +} + +template +void genclusterw(int x, int y, int x0, int y0, int x1, int y1, + std::vector& cells, RNG& rng, double startp = 0.5, + double decayp = 0.9) +{ + std::vector add; + + auto maybe_add = [&](int x_, int y_) { + Cell2D c(x_, y_); + // if (std::uniform_real_distribution()(rng) < startp && + // !rangeContainsValue(cells, c)) { + // cells.push_back(c); + // add.push_back(c); + // } + }; + + // NORTH + if (y < y1) { + maybe_add(x, y + 1); + } + // NORTHEAST + if (x < x1 && y < y1) { + maybe_add(x + 1, y + 1); + } + // EAST + if (x < x1) { + maybe_add(x + 1, y); + } + // SOUTHEAST + if (x < x1 && y > y0) { + maybe_add(x + 1, y - 1); + } + // SOUTH + if (y > y0) { + maybe_add(x, y - 1); + } + // SOUTHWEST + if (x > x0 && y > y0) { + maybe_add(x - 1, y - 1); + } + // WEST + if (x > x0) { + maybe_add(x - 1, y); + } + // NORTHWEST + if (x > x0 && y < y1) { + maybe_add(x - 1, y + 1); + } + + for (Cell2D& c : add) { + genclusterw(c.row, c.col, x0, y0, x1, y1, cells, rng, startp * decayp, + decayp); + } +} + +template +Cluster2D gencluster(int x0, int y0, int x1, int y1, RNG& rng, + double startp = 0.5, double decayp = 0.9) +{ + int x0_ = x0 + 1; + int x1_ = x1 - 1; + int y0_ = y0 + 1; + int y1_ = y1 - 1; + + int x = std::uniform_int_distribution(x0_, x1_)(rng); + int y = std::uniform_int_distribution(y0_, y1_)(rng); + + std::vector cells = {Cell2D(x, y)}; + genclusterw(x, y, x0_, y0_, x1_, y1_, cells, rng, startp, decayp); + + Cluster2D cl; + cl.cells = std::move(cells); + + return cl; +} + +//__________________________________________________ +void ClustererACTS::process(gsl::span digits, + gsl::span digitROFs, + std::vector& clusters, + std::vector& patterns, + std::vector& clusterROFs, + const ConstDigitTruth* digitLabels, + ClusterTruth* clusterLabels, + gsl::span digMC2ROFs, + std::vector* clusterMC2ROFs) +{ + if (!mThread) { + mThread = std::make_unique(this); + } + + auto* geom = o2::trk::GeometryTGeo::Instance(); + + for (size_t iROF = 0; iROF < digitROFs.size(); ++iROF) { + const auto& inROF = digitROFs[iROF]; + const auto outFirst = static_cast(clusters.size()); + const int first = inROF.getFirstEntry(); + const int nEntries = inROF.getNEntries(); + + if (nEntries == 0) { + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), outFirst, 0); + continue; + } + + // Sort digit indices within this ROF by (chipID, col, row) so we can process + // chip by chip, column by column -- the same ordering the ALPIDE scanner expects. + mSortIdx.resize(nEntries); + std::iota(mSortIdx.begin(), mSortIdx.end(), first); + std::sort(mSortIdx.begin(), mSortIdx.end(), [&digits](int a, int b) { + const auto& da = digits[a]; + const auto& db = digits[b]; + if (da.getChipIndex() != db.getChipIndex()) { + return da.getChipIndex() < db.getChipIndex(); + } + if (da.getColumn() != db.getColumn()) { + return da.getColumn() < db.getColumn(); + } + return da.getRow() < db.getRow(); + }); + + // Type aliases for ACTS clustering + using Cell = Cell2D; + using CellCollection = std::vector; + using Cluster = Cluster2D; + using ClusterCollection = std::vector; + static constexpr int GridDim = 2; ///< Dimensionality of the clustering grid (2D for pixel detectors) + + CellCollection cells; // Input collection of cells (pixels) to be clustered + Acts::Ccl::ClusteringData data; // Internal data structure used by ACTS clustering algorithm + ClusterCollection clsCollection; // Output collection of clusters found by the algorithm + + // Process one chip at a time + int sliceStart = 0; + while (sliceStart < nEntries) { + const int chipFirst = sliceStart; + const uint16_t chipID = digits[mSortIdx[sliceStart]].getChipIndex(); + while (sliceStart < nEntries && digits[mSortIdx[sliceStart]].getChipIndex() == chipID) { + ++sliceStart; + } + const int chipN = sliceStart - chipFirst; + + // Fill cells from digits for this chip + cells.clear(); + data.clear(); + clsCollection.clear(); + cells.reserve(chipN); + for (int i = chipFirst; i < chipFirst + chipN; ++i) { + const auto& digit = digits[mSortIdx[i]]; + cells.emplace_back(digit.getRow(), digit.getColumn(), mSortIdx[i]); + } + + LOG(debug) << "Clustering with ACTS on chip " << chipID << " " << cells.size() << " digits"; + Acts::Ccl::createClusters(data, + cells, + clsCollection, + Acts::Ccl::DefaultConnect(false)); + + LOG(debug) << " found " << clsCollection.size() << " clusters"; + + // Convert ACTS clusters to O2 clusters + for (const auto& actsCluster : clsCollection) { + if (actsCluster.cells.empty()) { + continue; + } + + // Calculate bounding box + uint16_t rowMin = static_cast(actsCluster.cells[0].row); + uint16_t rowMax = rowMin; + uint16_t colMin = static_cast(actsCluster.cells[0].col); + uint16_t colMax = colMin; + + for (const auto& cell : actsCluster.cells) { + rowMin = std::min(rowMin, static_cast(cell.row)); + rowMax = std::max(rowMax, static_cast(cell.row)); + colMin = std::min(colMin, static_cast(cell.col)); + colMax = std::max(colMax, static_cast(cell.col)); + } + + const uint16_t rowSpan = rowMax - rowMin + 1; + const uint16_t colSpan = colMax - colMin + 1; + + // Check if cluster needs splitting (too large for pattern encoding) + const bool isHuge = rowSpan > o2::itsmft::ClusterPattern::MaxRowSpan || + colSpan > o2::itsmft::ClusterPattern::MaxColSpan; + + if (isHuge) { + // Split huge cluster into MaxRowSpan x MaxColSpan tiles + LOG(warning) << "Splitting huge TRK cluster: chipID " << chipID + << ", rows " << rowMin << ":" << rowMax + << " cols " << colMin << ":" << colMax; + + for (uint16_t tileColMin = colMin; tileColMin <= colMax; + tileColMin = static_cast(tileColMin + o2::itsmft::ClusterPattern::MaxColSpan)) { + uint16_t tileColMax = std::min(colMax, static_cast(tileColMin + o2::itsmft::ClusterPattern::MaxColSpan - 1)); + + for (uint16_t tileRowMin = rowMin; tileRowMin <= rowMax; + tileRowMin = static_cast(tileRowMin + o2::itsmft::ClusterPattern::MaxRowSpan)) { + uint16_t tileRowMax = std::min(rowMax, static_cast(tileRowMin + o2::itsmft::ClusterPattern::MaxRowSpan - 1)); + + // Collect cells in this tile + std::vector> tileCells; + for (const auto& cell : actsCluster.cells) { + uint16_t r = static_cast(cell.row); + uint16_t c = static_cast(cell.col); + if (r >= tileRowMin && r <= tileRowMax && c >= tileColMin && c <= tileColMax) { + tileCells.emplace_back(r, c); + } + } + + if (tileCells.empty()) { + continue; + } + + uint16_t tileRowSpan = tileRowMax - tileRowMin + 1; + uint16_t tileColSpan = tileColMax - tileColMin + 1; + + // Encode pattern for this tile + std::array patt{}; + for (const auto& [r, c] : tileCells) { + uint32_t ir = r - tileRowMin; + uint32_t ic = c - tileColMin; + int nbit = ir * tileColSpan + ic; + patt[nbit >> 3] |= (0x1 << (7 - (nbit % 8))); + } + patterns.emplace_back(static_cast(tileRowSpan)); + patterns.emplace_back(static_cast(tileColSpan)); + const int nBytes = (tileRowSpan * tileColSpan + 7) / 8; + patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes); + + // Handle MC labels for this tile + if (clusterLabels && digitLabels) { + const auto clsIdx = static_cast(clusters.size()); + for (const auto& cell : actsCluster.cells) { + uint16_t r = static_cast(cell.row); + uint16_t c = static_cast(cell.col); + if (r >= tileRowMin && r <= tileRowMax && c >= tileColMin && c <= tileColMax) { + if (cell.digitIdx < digitLabels->getIndexedSize()) { + const auto& lbls = digitLabels->getLabels(cell.digitIdx); + for (const auto& lbl : lbls) { + clusterLabels->addElement(clsIdx, lbl); + } + } + } + } + } + + // Create O2 cluster for this tile + o2::trk::Cluster cluster; + cluster.chipID = chipID; + cluster.row = tileRowMin; + cluster.col = tileColMin; + cluster.size = static_cast(tileCells.size()); + if (geom) { + cluster.subDetID = static_cast(geom->getSubDetID(chipID)); + cluster.layer = static_cast(geom->getLayer(chipID)); + cluster.disk = static_cast(geom->getDisk(chipID)); + } + clusters.emplace_back(cluster); + } + } + } else { + // Normal cluster - encode directly + std::array patt{}; + for (const auto& cell : actsCluster.cells) { + uint32_t ir = static_cast(cell.row - rowMin); + uint32_t ic = static_cast(cell.col - colMin); + int nbit = ir * colSpan + ic; + patt[nbit >> 3] |= (0x1 << (7 - (nbit % 8))); + } + patterns.emplace_back(static_cast(rowSpan)); + patterns.emplace_back(static_cast(colSpan)); + const int nBytes = (rowSpan * colSpan + 7) / 8; + patterns.insert(patterns.end(), patt.begin(), patt.begin() + nBytes); + + // Handle MC labels + if (clusterLabels && digitLabels) { + const auto clsIdx = static_cast(clusters.size()); + for (const auto& cell : actsCluster.cells) { + if (cell.digitIdx < digitLabels->getIndexedSize()) { + const auto& lbls = digitLabels->getLabels(cell.digitIdx); + for (const auto& lbl : lbls) { + clusterLabels->addElement(clsIdx, lbl); + } + } + } + } + + // Create O2 cluster + o2::trk::Cluster cluster; + cluster.chipID = chipID; + cluster.row = rowMin; + cluster.col = colMin; + cluster.size = static_cast(actsCluster.cells.size()); + if (geom) { + cluster.subDetID = static_cast(geom->getSubDetID(chipID)); + cluster.layer = static_cast(geom->getLayer(chipID)); + cluster.disk = static_cast(geom->getDisk(chipID)); + } + clusters.emplace_back(cluster); + } + } + + LOG(debug) << " clusterization of chip " << chipID << " completed!"; + } + clusterROFs.emplace_back(inROF.getBCData(), inROF.getROFrame(), + 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); + // } + // } +} diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt index ab817a3fdaa0d..6d30d8d01bb12 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/CMakeLists.txt @@ -10,7 +10,8 @@ # or submit itself to any jurisdiction. o2_add_library(TRKSimulation - SOURCES src/TRKLayer.cxx + SOURCES src/Hit.cxx + src/TRKLayer.cxx src/ChipDigitsContainer.cxx src/ChipSimResponse.cxx src/Detector.cxx @@ -18,9 +19,8 @@ o2_add_library(TRKSimulation src/Digitizer.cxx src/TRKServices.cxx src/DPLDigitizerParam.cxx - src/TRKPetalCase.cxx - src/TRKPetalLayer.cxx - src/TRKPetalDisk.cxx + src/VDLayer.cxx + src/VDGeometryBuilder.cxx PUBLIC_LINK_LIBRARIES O2::TRKBase O2::FT3Simulation O2::ITSMFTSimulation @@ -28,14 +28,15 @@ o2_add_library(TRKSimulation O2::SimulationDataFormat) o2_target_root_dictionary(TRKSimulation - HEADERS include/TRKSimulation/ChipDigitsContainer.h + HEADERS include/TRKSimulation/Hit.h + include/TRKSimulation/ChipDigitsContainer.h include/TRKSimulation/ChipSimResponse.h include/TRKSimulation/DigiParams.h include/TRKSimulation/Digitizer.h include/TRKSimulation/Detector.h include/TRKSimulation/TRKLayer.h include/TRKSimulation/TRKServices.h - include/TRKSimulation/TRKPetalCase.h - include/TRKSimulation/TRKPetalLayer.h - include/TRKSimulation/TRKPetalDisk.h - include/TRKSimulation/DPLDigitizerParam.h) \ No newline at end of file + include/TRKSimulation/VDLayer.h + include/TRKSimulation/VDGeometryBuilder.h + include/TRKSimulation/VDSensorRegistry.h + include/TRKSimulation/DPLDigitizerParam.h) diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h index 658fb823bb596..bf28ace0724bc 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/ChipDigitsContainer.h @@ -29,6 +29,16 @@ class ChipDigitsContainer : public o2::itsmft::ChipDigitsContainer using Segmentation = SegmentationChip; + /// Get global ordering key made of readout frame, column and row + static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col) + { + return (static_cast(roframe) << (8 * sizeof(UInt_t))) + (static_cast(col) << (8 * sizeof(Short_t))) + row; + } + + /// Adds noise digits, deleted the one using the itsmft::DigiParams interface + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::itsmft::DigiParams* params, int maxRows = o2::itsmft::SegmentationAlpide::NRows, int maxCols = o2::itsmft::SegmentationAlpide::NCols) = delete; + void addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer); + ClassDefNV(ChipDigitsContainer, 1); }; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h index 584ffaa3aff75..de839b27aefee 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DPLDigitizerParam.h @@ -32,14 +32,14 @@ struct DPLDigitizerParam : public o2::conf::ConfigurableParamHelper @@ -31,11 +30,9 @@ namespace trk class Detector : public o2::base::DetImpl { public: - static constexpr Int_t mNumberOfVolumes = 44; /// hardcoded for the current geometry = 8 MLOT layers + 36 volumes in the VD. TODO: automatize or change according to the current geometry - static constexpr Int_t mNumberOfVolumesVD = 36; /// hardcoded for the current geometry = 36 volumes in the VD. TODO: automatize or change according to the current geometry - Detector(bool active); Detector(); + Detector(const Detector& other); ~Detector(); // Factory method @@ -46,9 +43,9 @@ class Detector : public o2::base::DetImpl void ConstructGeometry() override; - o2::itsmft::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, - unsigned char startStatus, unsigned char endStatus); + o2::trk::Hit* addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, + unsigned char startStatus, unsigned char endStatus); // Mandatory overrides void BeginPrimary() override { ; } @@ -61,8 +58,8 @@ class Detector : public o2::base::DetImpl void Register() override; void Reset() override; - // Custom memer functions - std::vector* getHits(int iColl) const + // Custom member functions + std::vector* getHits(int iColl) const { if (!iColl) { return mHits; @@ -70,8 +67,7 @@ class Detector : public o2::base::DetImpl return nullptr; } - void configDefault(); - void buildTRKNewVacuumVessel(); + void configMLOT(); void configFromFile(std::string fileName = "alice3_TRK_layout.txt"); void configToFile(std::string fileName = "alice3_TRK_layout.txt"); @@ -80,19 +76,21 @@ class Detector : public o2::base::DetImpl void createGeometry(); private: + int mNumberOfVolumes; + int mNumberOfVolumesVD; + // Transient data about track passing the sensor struct TrackData { - bool mHitStarted; // hit creation started - unsigned char mTrkStatusStart; // track status flag - TLorentzVector mPositionStart; // position at entrance - TLorentzVector mMomentumStart; // momentum - double mEnergyLoss; // energy loss - } mTrackData; //! transient data - GeometryTGeo* mGeometryTGeo; //! - std::vector* mHits; // ITSMFT ones for the moment - std::vector mLayers; - TRKServices mServices; // Houses the services of the TRK, but not the Iris tracker - std::vector mPetalCases; // Houses the Iris tracker and its services. Created fully in the beam pipe + bool mHitStarted; // hit creation started + unsigned char mTrkStatusStart; // track status flag + TLorentzVector mPositionStart; // position at entrance + TLorentzVector mMomentumStart; // momentum + double mEnergyLoss; // energy loss + } mTrackData; //! transient data + GeometryTGeo* mGeometryTGeo; //! + std::vector* mHits; // Derived from ITSMFT + std::vector> mLayers; + TRKServices mServices; // Houses the services of the TRK, but not the Iris tracker std::vector mFirstOrLastLayers; // Names of the first or last layers bool InsideFirstOrLastLayer(std::string layerName); @@ -106,14 +104,12 @@ class Detector : public o2::base::DetImpl public: static constexpr Int_t sNumberVDPetalCases = 4; //! Number of VD petals int getNumberOfLayers() const { return mLayers.size(); } //! Number of TRK layers - int getNumberOfLayersVD() const { return mPetalCases[0].mPetalLayers.size(); } - int getNumberOfDisksVD() const { return mPetalCases[0].mPetalDisks.size(); } - void Print(FairVolume* vol, int volume, int subDetID, int layer, int stave, int halfstave, int chipID) const; + void Print(FairVolume* vol, int volume, int subDetID, int layer, int stave, int halfstave, int mod, int chip, int chipID) const; template friend class o2::base::DetImpl; - ClassDefOverride(Detector, 1); + ClassDefOverride(Detector, 2); }; } // namespace trk } // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h index 739ac5c9bd617..d7d1ea28bfcf7 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/DigiParams.h @@ -15,8 +15,12 @@ #ifndef ALICEO2_TRK_DIGIPARAMS_H #define ALICEO2_TRK_DIGIPARAMS_H +#include + #include -#include +#include "ITSMFTSimulation/AlpideSignalTrapezoid.h" +#include "ITSMFTSimulation/AlpideSimResponse.h" +#include "TRKBase/AlmiraParam.h" #include "TRKBase/TRKBaseParam.h" #include "TRKBase/GeometryTGeo.h" @@ -49,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); @@ -91,8 +92,8 @@ class DigiParams bool isTimeOffsetSet() const { return mTimeOffset > -infTime; } - const o2::trk::ChipSimResponse* getAlpSimResponse() const { return mAlpSimResponse; } - void setAlpSimResponse(const o2::trk::ChipSimResponse* par) { mAlpSimResponse = par; } + 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; } @@ -101,18 +102,12 @@ class DigiParams private: static constexpr double infTime = 1e99; - bool mIsContinuous = false; ///< flag for continuous simulation - float mNoisePerPixel = 1.e-8; ///< ALPIDE 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 = 150; ///< charge threshold in Nelectrons - int mMinChargeToAccount = 15; ///< minimum charge contribution to account - int mNSimSteps = 18; ///< number of steps in response simulation - float mNSimStepsInv = 0; ///< its inverse + float mNoisePerPixel = 1.e-7; ///< Noise per chip + double mTimeOffset = -2 * infTime; ///< time offset (in seconds!) to calculate ROFrame from hit time + int mChargeThreshold = 75; ///< charge threshold in Nelectrons + int mMinChargeToAccount = 7; ///< minimum charge contribution to account + int mNSimSteps = 475; ///< number of steps in response simulation + float mNSimStepsInv = 1. / mNSimSteps; ///< its inverse float mEnergyToNElectrons = 1. / 3.6e-9; // conversion of eloss to Nelectrons @@ -120,16 +115,22 @@ 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 - const o2::trk::ChipSimResponse* mAlpSimResponse = nullptr; //!< 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); }; } // namespace trk } // namespace o2 -#endif \ No newline at end of file +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h index 573217fe9b076..5910fc98134aa 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Digitizer.h @@ -25,7 +25,7 @@ #include "TRKSimulation/ChipDigitsContainer.h" #include "TRKSimulation/DigiParams.h" -#include "ITSMFTSimulation/Hit.h" +#include "TRKSimulation/Hit.h" #include "TRKBase/GeometryTGeo.h" #include "DataFormatsITSMFT/Digit.h" #include "DataFormatsITSMFT/ROFRecord.h" @@ -45,28 +45,30 @@ class Digitizer void setDigits(std::vector* dig) { mDigits = dig; } void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } void setROFRecords(std::vector* rec) { mROFRecords = rec; } + void setResponseName(const std::string& name) { mRespName = name; } o2::trk::DigiParams& getParams() { return (o2::trk::DigiParams&)mParams; } const o2::trk::DigiParams& getParams() const { return mParams; } void init(); - o2::trk::ChipSimResponse* getChipResponse(int chipID); + 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); - - void setDigiParams(const o2::trk::DigiParams& par) { mParams = par; } 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::itsmft::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) { @@ -102,15 +104,13 @@ class Digitizer /// Get the number of columns according to the subdetector /// \param subDetID 0 for VD, 1 for ML/OT /// \param layer 0 to 2 for VD, 0 to 7 for ML/OT - /// \return Number of columns (for the moment, in the entire layer(VD) or stave (ML/OT) + /// \return Number of columns (In the entire layer(VD) or chip (ML/OT) int getNCols(int subDetID, int layer) { if (subDetID == 0) { // VD return constants::VD::petal::layer::nCols; - } else if (subDetID == 1 && layer <= 3) { // ML - return constants::ML::nCols; - } else if (subDetID == 1 && layer >= 4) { // OT - return constants::OT::nCols; + } else if (subDetID == 1) { // ML/OT: the smallest element is a chip of 470 rows and 640 cols + return constants::moduleMLOT::chip::nCols; } return 0; } @@ -118,15 +118,13 @@ class Digitizer /// Get the number of rows according to the subdetector /// \param subDetID 0 for VD, 1 for ML/OT /// \param layer 0 to 2 for VD, 0 to 7 for ML/OT - /// \return Number of rows (for the moment, in the entire layer(VD) or stave (ML/OT) + /// \return Number of rows (In the entire layer(VD) or chip (ML/OT) int getNRows(int subDetID, int layer) { if (subDetID == 0) { // VD return constants::VD::petal::layer::nRows[layer]; - } else if (subDetID == 1 && layer <= 3) { // ML - return constants::ML::nRows; - } else if (subDetID == 1 && layer >= 4) { // OT - return constants::OT::nRows; + } else if (subDetID == 1) { // ML/OT + return constants::moduleMLOT::chip::nRows; } return 0; } @@ -141,17 +139,18 @@ class Digitizer uint32_t mROFrameMax = 0; ///< highest RO frame of current digits uint32_t mNewROFrame = 0; ///< ROFrame corresponding to provided time + bool mIsBeforeFirstRO = false; + uint32_t mEventROFrameMin = 0xffffffff; ///< lowest RO frame for processed events (w/o automatic noise ROFs) uint32_t mEventROFrameMax = 0; ///< highest RO frame forfor processed events (w/o automatic noise ROFs) int mNumberOfChips = 0; - o2::trk::ChipSimResponse* mChipSimResp = nullptr; // simulated response - o2::trk::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips - o2::trk::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips + const o2::trk::ChipSimResponse* mChipSimResp = nullptr; // simulated response + const o2::trk::ChipSimResponse* mChipSimRespVD = nullptr; // simulated response for VD chips + const o2::trk::ChipSimResponse* mChipSimRespMLOT = nullptr; // simulated response for ML/OT chips - // std::string mResponseFile = "$(O2_ROOT)/share/Detectors/ITSMFT/data/AlpideResponseData/AlpideResponseData.root"; - std::string mResponseFile = "$(O2_ROOT)/share/Detectors/Upgrades/ITS3/data/ITS3ChipResponseData/APTSResponseData.root"; /// using temporarly the APTS response + std::string mRespName; /// APTS or ALICE3, depending on the response to be used bool mSimRespOrientation{false}; // wether the orientation in the response function is flipped float mSimRespVDShift{0.f}; // adjusting the Y-shift in the APTS response function to match sensor local coord. diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h new file mode 100644 index 0000000000000..402a343ead472 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Hit.h @@ -0,0 +1,29 @@ +// 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 Hit.h +/// \brief Definition of the TRK Hit class + +#ifndef ALICEO2_TRK_HIT_H_ +#define ALICEO2_TRK_HIT_H_ + +#include "ITSMFTSimulation/Hit.h" + +namespace o2::trk +{ +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 ba894f6d7a92b..e900cfa679ffe 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKLayer.h @@ -12,55 +12,149 @@ #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 +#include namespace o2 { namespace trk { -class TRKLayer +enum class MatBudgetParamMode { + Thickness, + X2X0 +}; + +class TRKCylindricalLayer { public: - TRKLayer() = default; - TRKLayer(int layerNumber, std::string layerName, float rInn, float rOut, float zLength, float layerX2X0); - TRKLayer(int layerNumber, std::string layerName, float rInn, float zLength, float thick); - ~TRKLayer() = default; - - void setLayout(eLayout layout) { mLayout = layout; }; + TRKCylindricalLayer() = default; + TRKCylindricalLayer(int layerNumber, std::string layerName, float rInn, float length, float thickOrX2X0, MatBudgetParamMode mode); + virtual ~TRKCylindricalLayer() = default; auto getInnerRadius() const { return mInnerRadius; } auto getOuterRadius() const { return mOuterRadius; } - auto getZ() const { return mZ; } + auto getZ() const { return mLength; } auto getx2X0() const { return mX2X0; } auto getChipThickness() const { return mChipThickness; } auto getNumber() const { return mLayerNumber; } auto getName() const { return mLayerName; } - TGeoVolume* createSensor(std::string type, double width = -1); - TGeoVolume* createChip(std::string type, double width = -1); - TGeoVolume* createStave(std::string type, double width = -1); - void createLayer(TGeoVolume* motherVolume); - - private: - // TGeo objects outside logical volumes can cause errors. Only used in case of kStaggered and kTurboStaves layouts - static constexpr float mLogicalVolumeThickness = 1; + virtual TGeoVolume* createSensor(); + virtual TGeoVolume* createMetalStack(); + virtual void createLayer(TGeoVolume* motherVolume); + protected: + // User defined parameters for the layer, to be set in the constructor int mLayerNumber; std::string mLayerName; float mInnerRadius; float mOuterRadius; - float mZ; + float mLength; float mX2X0; float mChipThickness; - float mModuleWidth; // u.m. = cm - eLayout mLayout; - ClassDef(TRKLayer, 1); + // Fixed parameters for the layer, to be set based on the specifications of the chip and module + static constexpr double sSensorThickness = constants::moduleMLOT::silicon::thickness; + + static constexpr float Si_X0 = 9.5f; + + ClassDef(TRKCylindricalLayer, 0); +}; + +class TRKSegmentedLayer : public TRKCylindricalLayer +{ + public: + TRKSegmentedLayer() = default; + TRKSegmentedLayer(int layerNumber, std::string layerName, float rInn, float tiltAngle, int numberOfStaves, int numberOfModules, float thickOrX2X0, MatBudgetParamMode mode); + ~TRKSegmentedLayer() override = default; + + TGeoVolume* createSensor() override; + TGeoVolume* createDeadzone(); + TGeoVolume* createMetalStack() override; + TGeoVolume* createChip(); + TGeoVolume* createModule(); + virtual TGeoVolume* createStave() = 0; + 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; + static constexpr double sChipLength = constants::moduleMLOT::chip::length; + static constexpr double sDeadzoneWidth = constants::moduleMLOT::chip::passiveEdgeReadOut; + static constexpr double sModuleLength = constants::moduleMLOT::length; + static constexpr double sModuleWidth = constants::moduleMLOT::width; + static constexpr int sHalfNumberOfChips = 4; + + // 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); +}; + +class TRKMLLayer : public TRKSegmentedLayer +{ + public: + TRKMLLayer() = default; + 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); +}; + +class TRKOTLayer : public TRKSegmentedLayer +{ + public: + TRKOTLayer() = default; + 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; + + // 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/TRKPetalCase.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalCase.h deleted file mode 100644 index cd45cc98fd177..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalCase.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. - -#ifndef ALICEO2_TRK_PETALCASE_H -#define ALICEO2_TRK_PETALCASE_H - -#include - -#include "TRKSimulation/TRKPetalLayer.h" -#include "TRKSimulation/TRKPetalDisk.h" -#include "TGeoCompositeShape.h" - -namespace o2 -{ -namespace trk -{ -class TRKPetalCase -{ - public: - TRKPetalCase() = default; - TRKPetalCase(Int_t number, TGeoVolume* motherVolume, Bool_t irisOpen); - ~TRKPetalCase() = default; - - // Sensitive volume list - std::vector mPetalLayers; - std::vector mPetalDisks; - - auto getPetalCaseName() { return mPetalCaseName; } - TString getFullName(); - - private: - void constructCase(TGeoVolume* motherVolume); - void constructColdPlate(TGeoVolume* motherVolume); - void constructDetectionPetals(TGeoVolume* motherVolume); - void addDetectionPetelsToFullComposite(); - - void addToPetalCaseComposite(TString shape) { mFullCompositeFormula += ("+" + shape); } - - Int_t mPetalCaseNumber; // Used to determine rotation and position. 0-3 - Bool_t mOpenState; // At injection energy, the iris tracker is in the open position. During stable beams, it is closed - - TString mPetalCaseName; - TString mFullCompositeFormula; // Used to excavate the petal and all its components from the vacuum - - // Center position of the petal case. 0,0,0 at stable beams (a.k.a. closed state) - Double_t mXPos, mYPos, mZPos; - - Double_t mWallThickness; // cm // Assume all the walls have the same thickness for now - Double_t mRIn; // cm - Double_t mROut; // cm - Double_t mRInOpenState; // cm - Double_t mPetalCaseLength; // cm - - Double_t mAngularCoverageAzimuthalWall; // Rad // Angular coverage of azimuthal part of wall (equivalent to that of the sensitive volumes) - Double_t mAngularCoverageRadialWall; // Rad // Angular coverage of radial part of wall - Double_t mToDeg; - - // Petal case parts -> In one composite shape - TGeoTubeSeg* mInnerAzimuthalWall; - TGeoTubeSeg* mOuterAzimuthalWall; - TGeoTubeSeg* mRadialWall; - TGeoTubeSeg* mForwardWall; - - TGeoRotation* mAzimuthalWallRot; - TGeoRotation* mRadialWall1Rot; - TGeoRotation* mRadialWall2Rot; - - TGeoCombiTrans* mAzimuthalWallCombiTrans; - TGeoCombiTrans* mRadialWall1CombiTrans; - TGeoCombiTrans* mRadialWall2CombiTrans; - TGeoCombiTrans* mForwardWall1CombiTrans; - TGeoCombiTrans* mForwardWall2CombiTrans; - - TGeoVolume* mPetalCaseVolume; - - // Cold plate - TGeoTubeSeg* mColdPlate; - TGeoVolume* mColdPlateVolume; - - ClassDef(TRKPetalCase, 1); -}; - -} // namespace trk -} // namespace o2 -#endif // ALICEO2_TRK_PETALCASE_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalDisk.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalDisk.h deleted file mode 100644 index 465f52eb8d41b..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalDisk.h +++ /dev/null @@ -1,64 +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 TRKPetalDisk.h -/// \brief Definition of the TRKPetalDisk class - -#ifndef ALICEO2_TRK_PETAL_DISK_H_ -#define ALICEO2_TRK_PETAL_DISK_H_ - -#include "TGeoManager.h" // for gGeoManager -#include "Rtypes.h" // for Double_t, Int_t, Bool_t, etc -#include // for LOG - -namespace o2 -{ -namespace trk -{ - -/// This class defines the Geometry for the TRK Disk TGeo. -class TRKPetalDisk -{ - public: - TRKPetalDisk() = default; - TRKPetalDisk(Int_t diskNumber, std::string diskName, Float_t z, Float_t rIn, Float_t rOut, Float_t angularCoverage, Float_t Diskx2X0); - ~TRKPetalDisk() = default; - - auto getInnerRadius() const { return mInnerRadius; } - auto getOuterRadius() const { return mOuterRadius; } - auto getThickness() const { return mChipThickness; } - auto getAngularCoverage() const { return mAngularCoverage; } - auto getZ() const { return mZ; } - auto getx2X0() const { return mx2X0; } - auto getName() const { return mDiskName; } - auto getSensorName() const { return mSensorName; } - - /// Creates the actual Disk and places inside its mother volume - /// \param motherVolume the TGeoVolume owing the volume structure - void createDisk(TGeoVolume* motherVolume, TGeoCombiTrans* combiTrans); - - private: - Int_t mDiskNumber = -1; ///< Current disk number - std::string mDiskName; ///< Current disk name - std::string mSensorName; - Double_t mInnerRadius; ///< Inner radius of this disk - Double_t mOuterRadius; ///< Outer radius of this disk - Double_t mAngularCoverage; - Double_t mZ; ///< Z position of the disk - Double_t mChipThickness; ///< Chip thickness - Double_t mx2X0; ///< Disk material budget x/X0 - - ClassDef(TRKPetalDisk, 1); -}; -} // namespace trk -} // namespace o2 - -#endif // ALICEO2_TRK_PETAL_DISK_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalLayer.h deleted file mode 100644 index 4e7a7735d51f0..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKPetalLayer.h +++ /dev/null @@ -1,61 +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 ALICEO2_TRK_PETAL_LAYER_H -#define ALICEO2_TRK_PETAL_LAYER_H - -#include "TGeoManager.h" -#include -#include "TGeoTube.h" - -#include "TRKBase/TRKBaseParam.h" - -namespace o2 -{ -namespace trk -{ -class TRKPetalLayer -{ - public: - TRKPetalLayer() = default; - TRKPetalLayer(Int_t layerNumber, std::string layerName, Float_t rIn, Float_t angularCoverage, Float_t zLength, Float_t layerX2X0); - ~TRKPetalLayer() = default; - - auto getInnerRadius() const { return mInnerRadius; } - auto getAngularCoverage() const { return mAngularCoverage; } - auto getZLength() { return mZ; } - auto getx2X0() const { return mX2X0; } - auto getChipThickness() const { return mChipThickness; } - auto getNumber() const { return mLayerNumber; } - auto getName() const { return mLayerName; } - auto getSensorName() const { return mSensorName; } - - void createLayer(TGeoVolume* motherVolume, TGeoCombiTrans* combiTrans); - - private: - Int_t mLayerNumber; - std::string mLayerName; - std::string mSensorName; - Float_t mInnerRadius; - Float_t mZ; - Float_t mX2X0; - Float_t mChipThickness; - Float_t mModuleWidth; // u.m. = cm - Float_t mAngularCoverage; // rad - - TGeoTubeSeg* mLayer; - - ClassDef(TRKPetalLayer, 1); -}; - -} // namespace trk -} // namespace o2 -#endif // ALICEO2_TRK_PETAL_LAYER_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/TRKServices.h index 8dd3968743024..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,9 @@ 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(); void excavateFromVacuum(TString shapeToExcavate); void registerVacuum(TGeoVolume* motherVolume); @@ -65,16 +69,18 @@ class TRKServices : public FairModule float mColdPlateX0; // Services + float mFiberArea = 7.1e-2; // cm^2 + float mPowerBundleArea = 1.13; // cm^2 float mFiberComposition[2] = {0.5, 0.5}; // SiO2, PE - float mPowerBundleComposition[2] = {0.09, 0.91}; // Cu, PE + float mPowerBundleComposition[2] = {0.08, 0.92}; // Cu, PE (with jacket) float mPowerBundleJacketComposition[2] = {0.06, 0.94}; // Cu, PE float mWaterBundleComposition[2] = {0.56, 0.44}; // PU, H2O float mWaterBundleDiskComposition[2] = {0.44, 0.56}; // PU, H2O - float mMiddleDiskThickness = 1.0; // cm + // float mMiddleDiskThickness = 1.0; // cm std::vector mCableFanWeights = {0.5, 0.3, 0.2}; // relative weights of the fan layers ClassDefOverride(TRKServices, 1); }; } // 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/include/TRKSimulation/VDGeometryBuilder.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h new file mode 100644 index 0000000000000..77b45c97cba84 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDGeometryBuilder.h @@ -0,0 +1,38 @@ +// 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_VDGEOMETRYBUILDER_H +#define O2_TRK_VDGEOMETRYBUILDER_H + +class TGeoVolume; + +#include +#include + +namespace o2::trk +{ + +// Build full VD for each design. +// Each function builds one local petal assembly (walls + layers + disks) +// and then places/rotates the petal once into the mother volume. + +void createIRISGeometryFullCyl(TGeoVolume* motherVolume); // Full-cylinder IRIS geometry (no petals, no gaps, no side walls) +void createIRISGeometry3InclinedWalls(TGeoVolume* motherVolume); // Full-cylinder IRIS geometry with 3 inclined walls +void createIRISGeometryFullCylwithDisks(TGeoVolume* motherVolume); // Full-cylinder IRIS geometry (no petals, no gaps, no side walls) incl. disks +void createIRIS4Geometry(TGeoVolume* motherVolume); // 4 petals, cylindrical L0 +void createIRIS4aGeometry(TGeoVolume* motherVolume); // 3 petals, cylindrical L0 +void createIRIS5Geometry(TGeoVolume* motherVolume); // 4 petals, rectangular L0 + +void createSinglePetalDebug(TGeoVolume* motherVolume, int petalID = 0, int nPetals = 4, bool rectangularL0 = false); + +} // namespace o2::trk + +#endif // O2_TRK_VDGEOMETRYBUILDER_H \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h new file mode 100644 index 0000000000000..acf9b19342e4b --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDLayer.h @@ -0,0 +1,117 @@ +// 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_VD_LAYER_H +#define ALICEO2_VD_LAYER_H + +#include +#include + +class TGeoVolume; +class TGeoMatrix; + +namespace o2 +{ +namespace trk +{ + +// Base class for a VD layer +class VDLayer +{ + public: + VDLayer() = default; + VDLayer(int layerNumber, const std::string& layerName, double layerX2X0); + virtual ~VDLayer() = default; + + // Create the layer (AIR container + sensors) and insert it into mother + virtual void createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans = nullptr) const = 0; + + double getChipThickness() const { return mChipThickness; } + + protected: + int mLayerNumber{0}; + std::string mLayerName; + double mX2X0{0.f}; // Radiation length in units of X0 + double mChipThickness{0.f}; // thickness derived from X/X0 + double mSensorThickness{0.f}; // + double mModuleWidth{4.54f}; // cm + + // ClassDef(VDLayer, 1) +}; + +// Cylindrical segment layer +class VDCylindricalLayer : public VDLayer +{ + public: + VDCylindricalLayer(int layerNumber, const std::string& layerName, double layerX2X0, + double radius, double phiSpanDeg, double lengthZ, double lengthSensZ); + + TGeoVolume* createSensor() const; // builds the sensor volume + TGeoVolume* createChip() const; + TGeoVolume* createMetalStack() const; + void createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans = nullptr) const override; + + private: + double mRadius{0.f}; + double mPhiSpanDeg{0.f}; // degrees + double mLengthZ{0.f}; // layer container length in Z + double mLengthSensZ{0.f}; // sensor length in Z + + // ClassDef(VDCylindricalLayer, 1) +}; + +// Rectangular segment layer +class VDRectangularLayer : public VDLayer +{ + public: + VDRectangularLayer(int layerNumber, const std::string& layerName, double layerX2X0, + double width, double lengthZ, double lengthSensZ); + + TGeoVolume* createSensor() const; + TGeoVolume* createChip() const; + TGeoVolume* createMetalStack() const; + void createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans = nullptr) const override; + + private: + double mWidth{0.f}; + double mLengthZ{0.f}; + double mLengthSensZ{0.f}; + + // ClassDef(VDRectangularLayer, 1) +}; + +// Disk segment layer +class VDDiskLayer : public VDLayer +{ + public: + VDDiskLayer(int layerNumber, const std::string& layerName, double layerX2X0, + double rMin, double rMax, double phiSpanDeg, double zPos); + + TGeoVolume* createSensor() const; + TGeoVolume* createChip() const; + TGeoVolume* createMetalStack() const; + void createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans = nullptr) const override; + + double getZPosition() const { return mZPos; } + + private: + double mRMin{0.f}; + double mRMax{0.f}; + double mPhiSpanDeg{0.f}; // degrees + double mZPos{0.f}; // placement along Z + + // ClassDef(VDDiskLayer, 1) +}; + +} // namespace trk +} // namespace o2 + +#endif // ALICEO2_VD_LAYER_H diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h new file mode 100644 index 0000000000000..c4fa222e1f4ef --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/VDSensorRegistry.h @@ -0,0 +1,42 @@ +// 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_VDSENSORREGISTRY_H +#define O2_TRK_VDSENSORREGISTRY_H + +#include +#include + +namespace o2::trk +{ + +struct VDSensorDesc { + enum class Region { Barrel, + Disk }; + enum class Type { Curved, + Plane, + }; + std::string name; // sensor volume name + int petal = -1; + Region region = Region::Barrel; + Type type = Type::Curved; + int idx = -1; // layer or disk index +}; + +// Accessor (defined in VDGeometryBuilder.cxx) +std::vector& vdSensorRegistry(); + +// Utilities (defined in VDGeometryBuilder.cxx) +void clearVDSensorRegistry(); +void registerSensor(const std::string& volName, int petal, VDSensorDesc::Region region, VDSensorDesc::Type type, int idx); + +} // namespace o2::trk +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx index 9ed4a4bedf5c5..d8e6df8b6099c 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/ChipDigitsContainer.cxx @@ -15,3 +15,50 @@ using namespace o2::trk; ChipDigitsContainer::ChipDigitsContainer(UShort_t idx) : o2::itsmft::ChipDigitsContainer(idx) {} + +//______________________________________________________________________ +void ChipDigitsContainer::addNoise(UInt_t rofMin, UInt_t rofMax, const o2::trk::DigiParams* params, int subDetID, int layer) +{ + UInt_t row = 0; + UInt_t col = 0; + Int_t nhits = 0; + constexpr float ns2sec = 1e-9; + float mean = 0.f; + int nel = 0; + int maxRows = 0; + int maxCols = 0; + + // TODO: set different noise and threshold for VD and MLOT + if (subDetID == 0) { // VD + maxRows = constants::VD::petal::layer::nRows[layer]; // TODO: get the layer from the geometry + maxCols = constants::VD::petal::layer::nCols; + mean = params->getNoisePerPixel() * maxRows * maxCols; + nel = static_cast(params->getChargeThreshold() * 1.1); + } else { // ML/OT + maxRows = constants::moduleMLOT::chip::nRows; + maxCols = constants::moduleMLOT::chip::nCols; + mean = params->getNoisePerPixel() * maxRows * maxCols; + nel = static_cast(params->getChargeThreshold() * 1.1); + } + + LOG(debug) << "Adding noise for chip " << mChipIndex << " with mean " << mean << " and charge " << nel; + + for (UInt_t rof = rofMin; rof <= rofMax; rof++) { + nhits = gRandom->Poisson(mean); + for (Int_t i = 0; i < nhits; ++i) { + row = gRandom->Integer(maxRows); + col = gRandom->Integer(maxCols); + LOG(debug) << "Generated noise hit at ROF " << rof << ", row " << row << ", col " << col; + if (mNoiseMap && mNoiseMap->isNoisy(mChipIndex, row, col)) { + continue; + } + if (mDeadChanMap && mDeadChanMap->isNoisy(mChipIndex, row, col)) { + continue; + } + auto key = getOrderingKey(rof, row, col); + if (!findDigit(key)) { + addDigit(key, rof, row, col, nel, o2::MCCompLabel(true)); + } + } + } +} diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx index f5027310fa66d..196727b2c140f 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Detector.cxx @@ -9,23 +9,31 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#include +#include "TRKSimulation/Detector.h" + +#include "DetectorsBase/Stack.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 "DetectorsBase/Stack.h" -#include "ITSMFTSimulation/Hit.h" -#include "TRKSimulation/Detector.h" -#include "TRKBase/TRKBaseParam.h" +#include + +#include +#include -using o2::itsmft::Hit; +using o2::trk::Hit; namespace o2 { namespace trk { + float getDetLengthFromEta(const float eta, const float radius) { return 2. * (10. + radius * std::cos(2 * std::atan(std::exp(-eta)))); @@ -34,53 +42,38 @@ float getDetLengthFromEta(const float eta, const float radius) Detector::Detector() : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { } Detector::Detector(bool active) : o2::base::DetImpl("TRK", true), mTrackData(), - mHits(o2::utils::createSimVector()) + mHits(o2::utils::createSimVector()) { auto& trkPars = TRKBaseParam::Instance(); if (trkPars.configFile != "") { configFromFile(trkPars.configFile); } else { - buildTRKNewVacuumVessel(); + configMLOT(); configToFile(); configServices(); } - mSensorName.resize(mNumberOfVolumes); // hardcoded. TODO: change size when a different naming scheme for VD is in place. Ideally could be 4 petals + 8 layers = 12 - int VDvolume = 0; - for (int i = 0; i < 4; i++) { /// VD - for (int j = 0; j < 3; j++) { - mSensorName[VDvolume].Form("%s%d_%s%d_%s%d", GeometryTGeo::getTRKPetalPattern(), i, GeometryTGeo::getTRKPetalLayerPattern(), j, GeometryTGeo::getTRKSensorPattern(), j); - VDvolume++; - } - for (int j = 0; j < 6; j++) { - mSensorName[VDvolume].Form("%s%d_%s%d_%s%d", GeometryTGeo::getTRKPetalPattern(), i, GeometryTGeo::getTRKPetalDiskPattern(), j, GeometryTGeo::getTRKSensorPattern(), j); - VDvolume++; - } - } - - for (int i = 0; i < 8; i++) { /// MLOT - mSensorName[VDvolume].Form("%s%d", GeometryTGeo::getTRKSensorPattern(), i); - VDvolume++; - } - - for (auto vd : mSensorName) { - std::cout << "Volume name: " << vd << std::endl; - } - LOGP(info, "Summary of TRK configuration:"); for (auto& layer : mLayers) { - LOGP(info, "Layer: {} name: {} r: {} cm | z: {} cm | thickness: {} cm", layer.getNumber(), layer.getName(), layer.getInnerRadius(), layer.getZ(), layer.getChipThickness()); + LOGP(info, "Layer: {} name: {} r: {} cm | z: {} cm | thickness: {} cm", layer->getNumber(), layer->getName(), layer->getInnerRadius(), layer->getZ(), layer->getChipThickness()); } } +Detector::Detector(const Detector& other) + : o2::base::DetImpl(other), + mTrackData(), + mHits(o2::utils::createSimVector()) +{ +} + Detector::~Detector() { if (mHits) { @@ -94,63 +87,49 @@ void Detector::ConstructGeometry() createGeometry(); } -void Detector::configDefault() -{ - - // Build TRK detector according to the scoping document - - mLayers.clear(); - - LOGP(warning, "Loading Scoping Document configuration for ALICE3 TRK"); - // mLayers.emplace_back(0, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(0)}, 0.5f, 50.f, 100.e-4); - // mLayers.emplace_back(1, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(1)}, 1.2f, 50.f, 100.e-4); - // mLayers.emplace_back(2, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(2)}, 2.5f, 50.f, 100.e-4); - mLayers.emplace_back(0, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(0)}, 3.78f, 124.f, 100.e-3); - mLayers.emplace_back(1, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(1)}, 7.f, 124.f, 100.e-3); - mLayers.emplace_back(2, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(2)}, 12.f, 124.f, 100.e-3); - mLayers.emplace_back(3, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(3)}, 20.f, 124.f, 100.e-3); - mLayers.emplace_back(4, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(4)}, 30.f, 124.f, 100.e-3); - mLayers.emplace_back(5, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(5)}, 45.f, 258.f, 100.e-3); - mLayers.emplace_back(6, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(6)}, 60.f, 258.f, 100.e-3); - mLayers.emplace_back(7, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(7)}, 80.f, 258.f, 100.e-3); -} - -void Detector::buildTRKNewVacuumVessel() +void Detector::configMLOT() { - // Build the TRK detector according to changes proposed during - // https://indico.cern.ch/event/1407704/ - // to adhere to the changes that were presented at the ALICE 3 Upgrade days in March 2024 - // L3 -> 7 cm, L4 -> 9 cm + auto& trkPars = TRKBaseParam::Instance(); mLayers.clear(); - LOGP(warning, "Loading \"After Upgrade Days March 2024\" configuration for ALICE3 TRK"); - // mLayers.emplace_back(0, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(0)}, 0.5f, 50.f, 100.e-4); - // mLayers.emplace_back(1, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(1)}, 1.2f, 50.f, 100.e-4); - // mLayers.emplace_back(2, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(2)}, 2.5f, 50.f, 100.e-4); - mLayers.emplace_back(0, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(0)}, 7.f, 124.f, 100.e-3); - LOGP(info, "TRKLayer created. Name: {}", std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(0)}); - mLayers.emplace_back(1, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(1)}, 9.f, 124.f, 100.e-3); - mLayers.emplace_back(2, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(2)}, 12.f, 124.f, 100.e-3); - mLayers.emplace_back(3, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(3)}, 20.f, 124.f, 100.e-3); - mLayers.emplace_back(4, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(4)}, 30.f, 124.f, 100.e-3); - mLayers.emplace_back(5, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(5)}, 45.f, 258.f, 100.e-3); - mLayers.emplace_back(6, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(6)}, 60.f, 258.f, 100.e-3); - mLayers.emplace_back(7, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(7)}, 80.f, 258.f, 100.e-3); + const std::vector rInn{7.f, 9.f, 12.f, 20.f, 30.f, 45.f, 60.f, 80.f}; + const float thick = 100.e-3; - auto& trkPars = TRKBaseParam::Instance(); - - // Middle layers - mLayers[0].setLayout(trkPars.layoutML); - mLayers[1].setLayout(trkPars.layoutML); - mLayers[2].setLayout(trkPars.layoutML); - mLayers[3].setLayout(trkPars.layoutML); - - // Outer tracker - mLayers[4].setLayout(trkPars.layoutOL); - mLayers[5].setLayout(trkPars.layoutOL); - mLayers[6].setLayout(trkPars.layoutOL); - mLayers[7].setLayout(trkPars.layoutOL); + switch (trkPars.layoutMLOT) { + case kCylindrical: { + 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 < 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 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 < constants::ML::nLayers + constants::OT::nLayers; ++i) { + std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(i); + 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], tiltAngles[i], nStaves[i], nMods[i], thick, MatBudgetParamMode::Thickness)); + } + } + break; + } + default: + LOGP(fatal, "Unknown option {} for configMLOT", static_cast(trkPars.layoutMLOT)); + break; + } } void Detector::configFromFile(std::string fileName) @@ -161,6 +140,8 @@ void Detector::configFromFile(std::string fileName) LOGP(fatal, "File {} not found, aborting.", fileName); } + auto& trkPars = TRKBaseParam::Instance(); + mLayers.clear(); LOGP(info, "Overriding geometry of ALICE3 TRK using {} file.", fileName); @@ -179,7 +160,86 @@ void Detector::configFromFile(std::string fileName) while (getline(ss, substr, '\t')) { tmpBuff.push_back(std::stof(substr)); } - mLayers.emplace_back(layerCount, std::string{GeometryTGeo::getTRKLayerPattern() + std::to_string(layerCount)}, tmpBuff[0], tmpBuff[1], tmpBuff[2]); + + std::string name = GeometryTGeo::getTRKLayerPattern() + std::to_string(layerCount); + + switch (trkPars.layoutMLOT) { + 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: { + // 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 { + // 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; + } + default: + LOGP(fatal, "Unknown option {} for configMLOT", static_cast(trkPars.layoutMLOT)); + break; + } + ++layerCount; } } @@ -189,8 +249,8 @@ void Detector::configToFile(std::string fileName) LOGP(info, "Exporting TRK Detector layout to {}", fileName); std::ofstream conFile(fileName.c_str(), std::ios::out); conFile << "/// TRK configuration file: inn_radius z_length lay_thickness" << std::endl; - for (auto layer : mLayers) { - conFile << layer.getInnerRadius() << "\t" << layer.getZ() << "\t" << layer.getChipThickness() << std::endl; + for (const auto& layer : mLayers) { + conFile << layer->getInnerRadius() << "\t" << layer->getZ() << "\t" << layer->getChipThickness() << std::endl; } } @@ -255,18 +315,67 @@ void Detector::createGeometry() vTRK->SetTitle(vstrng); for (auto& layer : mLayers) { - layer.createLayer(vTRK); + layer->createLayer(vTRK); } // Add service for inner tracker mServices.createServices(vTRK); - mPetalCases.clear(); - // Add petal cases (the sensitive layers inside the petal cases get constructed here too) + + // Build the VD using the petal builder + // Choose the VD design based on TRKBaseParam.layoutVD auto& trkPars = TRKBaseParam::Instance(); - for (Int_t petalCaseNumber = 0; petalCaseNumber < sNumberVDPetalCases; ++petalCaseNumber) { - mPetalCases.emplace_back(petalCaseNumber, vTRK, trkPars.irisOpen); - mServices.excavateFromVacuum(mPetalCases[petalCaseNumber].getFullName()); + + o2::trk::clearVDSensorRegistry(); + + switch (trkPars.layoutVD) { + case kIRIS4: + LOG(info) << "Building VD with IRIS4 layout"; + o2::trk::createIRIS4Geometry(vTRK); + break; + case kIRISFullCyl: + LOG(info) << "Building VD with IRIS fully cylindrical layout"; + o2::trk::createIRISGeometryFullCyl(vTRK); + break; + case kIRISFullCyl3InclinedWalls: + LOG(info) << "Building VD with IRIS fully cylindrical layout with 3 inclined walls"; + o2::trk::createIRISGeometry3InclinedWalls(vTRK); + break; + case kIRIS5: + LOG(info) << "Building VD with IRIS5 layout"; + o2::trk::createIRIS5Geometry(vTRK); + break; + case kIRIS4a: + LOG(info) << "Building VD with IRIS4a layout"; + o2::trk::createIRIS4aGeometry(vTRK); + break; + default: + LOG(fatal) << "Unknown VD layout option: " << static_cast(trkPars.layoutVD); + break; + } + + // Fill sensor names from registry right after geometry creation + const auto& regs = o2::trk::vdSensorRegistry(); + mNumberOfVolumesVD = static_cast(regs.size()); + mNumberOfVolumes = mNumberOfVolumesVD + mLayers.size(); + mSensorName.resize(mNumberOfVolumes); + + // Fill VD sensor names from registry + int VDvolume = 0; + for (const auto& sensor : regs) { + mSensorName[VDvolume] = sensor.name; + VDvolume++; + } + + // Add MLOT sensor names + for (int i = 0; i < mLayers.size(); i++) { + mSensorName[VDvolume++].Form("%s%d", GeometryTGeo::getTRKSensorPattern(), i); } + + for (auto vd : mSensorName) { + std::cout << "Volume name: " << vd << std::endl; + } + + mServices.excavateFromVacuum("IRIS_CUTOUTsh"); mServices.registerVacuum(vTRK); } @@ -279,7 +388,7 @@ void Detector::InitializeO2Detector() mSensorID.resize(mNumberOfVolumes); // hardcoded. TODO: change size when a different namingh scheme for VD is in place. Ideally could be 4 petals + 8 layers = 12 for (int i = 0; i < mNumberOfVolumes; i++) { mSensorID[i] = gMC ? TVirtualMC::GetMC()->VolId(mSensorName[i]) : 0; // Volume ID from the Geant geometry - LOGP(info, "{}: mSensorID={}", i, mSensorID[i]); + LOGP(info, "{}: mSensorID={}, mSensorName={}", i, mSensorID[i], mSensorName[i].Data()); } } @@ -291,26 +400,18 @@ void Detector::defineSensitiveVolumes() TString volumeName; LOGP(info, "Adding TRK Sensitive Volumes"); - // Add petal case sensitive volumes - for (int petalCase = 0; petalCase < sNumberVDPetalCases; ++petalCase) { - // Petal layers - for (int petalLayer = 0; petalLayer < mPetalCases[petalCase].mPetalLayers.size(); ++petalLayer) { - volumeName = mPetalCases[petalCase].mPetalLayers[petalLayer].getSensorName(); - if (petalLayer == 0) { - mFirstOrLastLayers.push_back(volumeName.Data()); - } - LOGP(info, "Trying {}", volumeName.Data()); - v = geoManager->GetVolume(volumeName.Data()); - LOGP(info, "Adding TRK Sensitive Volume {}", v->GetName()); - AddSensitiveVolume(v); + // Register VD sensors created by VDGeometryBuilder + for (const auto& s : o2::trk::vdSensorRegistry()) { + TGeoVolume* v = gGeoManager->GetVolume(s.name.c_str()); + if (!v) { + LOGP(warning, "VD sensor volume '{}' not found", s.name); + continue; } - // Petal disks - for (int petalDisk = 0; petalDisk < mPetalCases[petalCase].mPetalDisks.size(); ++petalDisk) { - volumeName = mPetalCases[petalCase].mPetalDisks[petalDisk].getSensorName(); - LOGP(info, "Trying {}", volumeName.Data()); - v = geoManager->GetVolume(volumeName.Data()); - LOGP(info, "Adding TRK Sensitive Volume {}", v->GetName()); - AddSensitiveVolume(v); + LOGP(info, "Adding VD Sensitive Volume {}", v->GetName()); + AddSensitiveVolume(v); + // Optionally track first/last layers for TR references: + if (s.region == o2::trk::VDSensorDesc::Region::Barrel && (s.idx == 0 /*innermost*/)) { + mFirstOrLastLayers.push_back(s.name); } } @@ -369,7 +470,6 @@ bool Detector::ProcessHits(FairVolume* vol) int subDetID = -1; int layer = -1; int volume = 0; - int stave = -1; int volID = vol->getMCid(); bool notSens = false; @@ -444,16 +544,32 @@ bool Detector::ProcessHits(FairVolume* vol) if (stopHit) { TLorentzVector positionStop; fMC->TrackPosition(positionStop); + // Retrieve the indices with the volume path - int stave(0), halfstave(0); + int stave(0), halfstave(0), mod(0), chip(0); + + auto& trkPars = TRKBaseParam::Instance(); + if (subDetID == 1) { - fMC->CurrentVolOffID(1, halfstave); - fMC->CurrentVolOffID(2, stave); + if (trkPars.layoutMLOT == o2::trk::eMLOTLayout::kSegmented) { + fMC->CurrentVolOffID(1, chip); + fMC->CurrentVolOffID(2, mod); + if (mGeometryTGeo->getNumberOfHalfStaves(layer) == 2) { + fMC->CurrentVolOffID(3, halfstave); + fMC->CurrentVolOffID(4, stave); + } else if (mGeometryTGeo->getNumberOfHalfStaves(layer) == 1) { + fMC->CurrentVolOffID(3, stave); + } else { + LOGP(fatal, "Wrong number of halfstaves for layer {}", layer); + } + } } /// if VD, for the moment the volume is the "chipID" so no need to retrieve other elments - int chipID = mGeometryTGeo->getChipIndex(subDetID, volume, layer, stave, halfstave); + unsigned short chipID = mGeometryTGeo->getChipIndex(subDetID, volume, layer, stave, halfstave, mod, chip); + + // Print(vol, volume, subDetID, layer, stave, halfstave, mod, chip, chipID); - Print(vol, volume, subDetID, layer, stave, halfstave, chipID); + // mGeometryTGeo->Print(); Hit* p = addHit(stack->GetCurrentTrackNumber(), chipID, mTrackData.mPositionStart.Vect(), positionStop.Vect(), mTrackData.mMomentumStart.Vect(), mTrackData.mMomentumStart.E(), positionStop.T(), @@ -468,29 +584,43 @@ bool Detector::ProcessHits(FairVolume* vol) return true; } -o2::itsmft::Hit* Detector::addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos, - const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, - unsigned char endStatus) +o2::trk::Hit* Detector::addHit(int trackID, unsigned short detID, const TVector3& startPos, const TVector3& endPos, + const TVector3& startMom, double startE, double endTime, double eLoss, unsigned char startStatus, + unsigned char endStatus) { mHits->emplace_back(trackID, detID, startPos, endPos, startMom, startE, endTime, eLoss, startStatus, endStatus); return &(mHits->back()); } -void Detector::Print(FairVolume* vol, int volume, int subDetID, int layer, int stave, int halfstave, int chipID) const +void Detector::Print(FairVolume* vol, int volume, int subDetID, int layer, int stave, int halfstave, int mod, int chip, int chipID) const { int currentVol(0); LOG(info) << "Current volume name: " << fMC->CurrentVolName() << " and ID " << fMC->CurrentVolID(currentVol); LOG(info) << "volume: " << volume << "/" << mNumberOfVolumes - 1; - if (subDetID == 1 && mGeometryTGeo->getNumberOfHalfStaves(layer) == 2) { // staggered geometry - LOG(info) << "off volume name 1 " << fMC->CurrentVolOffName(1) << " halfstave: " << halfstave; - LOG(info) << "off volume name 2 " << fMC->CurrentVolOffName(2) << " stave: " << stave; - LOG(info) << "SubDetector ID: " << subDetID << " Layer: " << layer << " staveinLayer: " << stave << " Chip ID: " << chipID; - } else if (subDetID == 1 && mGeometryTGeo->getNumberOfHalfStaves(layer) == 1) { // turbo geometry - LOG(info) << "off volume name 2 " << fMC->CurrentVolOffName(2) << " stave: " << stave; - LOG(info) << "SubDetector ID: " << subDetID << " Layer: " << layer << " staveinLayer: " << stave << " Chip ID: " << chipID; + + auto& trkPars = TRKBaseParam::Instance(); + + if (subDetID == 1) { // MLOT + if (trkPars.layoutMLOT == o2::trk::eMLOTLayout::kCylindrical) { + LOG(info) << "off volume name 1 " << fMC->CurrentVolOffName(1) << " chip: " << chip; + LOG(info) << "SubDetector ID: " << subDetID << " Layer: " << layer << " Chip ID: " << chipID; + } else { + LOG(info) << "off volume name 1 " << fMC->CurrentVolOffName(1) << " chip: " << chip; + LOG(info) << "off volume name 2 " << fMC->CurrentVolOffName(2) << " module: " << mod; + if (mGeometryTGeo->getNumberOfHalfStaves(layer) == 2) { // staggered geometry + LOG(info) << "off volume name 3 " << fMC->CurrentVolOffName(3) << " halfstave: " << halfstave; + LOG(info) << "off volume name 4 " << fMC->CurrentVolOffName(4) << " stave: " << stave; + LOG(info) << "SubDetector ID: " << subDetID << " Layer: " << layer << " staveinLayer: " << stave << " Chip ID: " << chipID; + } else if (mGeometryTGeo->getNumberOfHalfStaves(layer) == 1) { // turbo geometry + LOG(info) << "off volume name 3 " << fMC->CurrentVolOffName(3) << " stave: " << stave; + LOG(info) << "SubDetector ID: " << subDetID << " Layer: " << layer << " staveinLayer: " << stave << " Chip ID: " << chipID; + } + } } else { + // VD LOG(info) << "SubDetector ID: " << subDetID << " Chip ID: " << chipID; } + LOG(info); } diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx index df6f46ac0ecb0..3558a6a87ce71 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/DigiParams.cxx @@ -12,9 +12,10 @@ /// \file DigiParams.cxx /// \brief Implementation of the TRK digitization steering params. Based on the ITS2 code. -#include // for LOG -#include "TRKSimulation/DigiParams.h" #include +#include "Framework/Logger.h" +#include "TRKSimulation/DigiParams.h" +#include "TRKSimulation/ChipSimResponse.h" using namespace o2::trk; @@ -24,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) @@ -58,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); @@ -70,3 +67,14 @@ void DigiParams::print() const printf("Charge time-response:\n"); mSignalShape.print(); } + +void DigiParams::setResponse(const o2::itsmft::AlpideSimResponse* resp) +{ + LOG(debug) << "Response function data path: " << resp->getDataPath(); + LOG(debug) << "Response function info: "; + // resp->print(); + if (!resp) { + LOGP(fatal, "cannot set response function from null"); + } + 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 cc89f0eff1a54..890c272fefbc2 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Digitizer.cxx @@ -23,10 +23,11 @@ #include #include #include +#include #include // for LOG using o2::itsmft::Digit; -using o2::itsmft::Hit; +using o2::trk::Hit; using Segmentation = o2::trk::SegmentationChip; using namespace o2::trk; @@ -48,43 +49,51 @@ void Digitizer::init() mChips[i].setDeadChanMap(mDeadChanMap); } } - // importing the charge collection tables - // (initialized while building O2) - auto file = TFile::Open(mResponseFile.data()); - if (!file) { - LOG(fatal) << "Cannot open response file " << mResponseFile; - } - // setting the correct response function (for the moment, for both VD and MLOT the APTS response function is udes) - mChipSimResp = (o2::trk::ChipSimResponse*)file->Get("response1"); + // setting the correct response function (for the moment, for both VD and MLOT the same response function is used) + mChipSimResp = mParams.getResponse(); mChipSimRespVD = mChipSimResp; /// for the moment considering the same response mChipSimRespMLOT = mChipSimResp; /// for the moment considering the same response /// setting scale factors to adapt to the APTS response function (adjusting pitch and Y shift) // TODO: adjust Y shift when the geometry is improved - LOG(debug) << " Depth max: " << mChipSimRespVD->getDepthMax(); - LOG(debug) << " Depth min: " << mChipSimRespVD->getDepthMin(); - - float thicknessVD = 0.0095; // cm --- hardcoded based on geometry currently present - float thicknessMLOT = 0.1; // cm --- hardcoded based on geometry currently present - - mSimRespVDScaleX = o2::trk::constants::apts::pitchX / o2::trk::SegmentationChip::PitchRowVD; - mSimRespVDScaleZ = o2::trk::constants::apts::pitchZ / o2::trk::SegmentationChip::PitchColVD; - mSimRespVDScaleDepth = o2::trk::constants::apts::thickness / (thicknessVD); /// introducing this scaling factor because the silicon thickness for the moment is 1 mm -> rescale to 45 um which is the depth of the APTS response - // mSimRespVDShift = mChipSimRespVD->getDepthMax() - thicknessVD * mSimRespVDScaleDepth / 2.f; // the shift should be done considering the rescaling done to adapt to the wrong silicon thickness. TODO: remove the scaling factor for the depth when the silicon thickness match the simulated response - mSimRespVDShift = mChipSimRespVD->getDepthMax(); // the curved, rescaled, sensors have a width from 0 to -45. Must add 10 um (= max depth) to match the APTS response. - mSimRespMLOTScaleX = o2::trk::constants::apts::pitchX / o2::trk::SegmentationChip::PitchRowMLOT; - mSimRespMLOTScaleZ = o2::trk::constants::apts::pitchZ / o2::trk::SegmentationChip::PitchColMLOT; - mSimRespMLOTScaleDepth = o2::trk::constants::apts::thickness / (thicknessMLOT); /// introducing this scaling factor because the silicon thickness for the moment is 1 mm -> rescale to 45 um which is the depth of the APTS response - mSimRespMLOTShift = mChipSimRespMLOT->getDepthMax() - thicknessMLOT * mSimRespMLOTScaleDepth / 2.f; // the shift should be done considering the rescaling done to adapt to the wrong silicon thickness. TODO: remove the scaling factor for the depth when the silicon thickness match the simulated response + LOG(info) << " Depth max VD: " << mChipSimRespVD->getDepthMax(); + LOG(info) << " Depth min VD: " << mChipSimRespVD->getDepthMin(); + + LOG(info) << " Depth max MLOT: " << mChipSimRespMLOT->getDepthMax(); + LOG(info) << " Depth min MLOT: " << mChipSimRespMLOT->getDepthMin(); + + float thicknessVD = 0.0095; // cm --- hardcoded based on geometry currently present + float thicknessMLOT = o2::trk::SegmentationChip::SiliconThicknessMLOT; // 0.01 cm = 100 um --- based on geometry currently present + + LOG(info) << "Using response name: " << mRespName; mSimRespOrientation = false; + if (mRespName == "APTS") { // default + mSimRespVDScaleX = o2::trk::constants::apts::pitchX / o2::trk::SegmentationChip::PitchRowVD; + mSimRespVDScaleZ = o2::trk::constants::apts::pitchZ / o2::trk::SegmentationChip::PitchColVD; + mSimRespVDShift = mChipSimRespVD->getDepthMax(); // the curved, rescaled, sensors have a width from 0 to -45. Must add ~10 um (= max depth) to match the APTS response. + mSimRespMLOTScaleX = o2::trk::constants::apts::pitchX / o2::trk::SegmentationChip::PitchRowMLOT; + mSimRespMLOTScaleZ = o2::trk::constants::apts::pitchZ / o2::trk::SegmentationChip::PitchColMLOT; + mSimRespOrientation = true; /// APTS response function is flipped along x wrt the ones of ALPIDE and ALICE3 + } else if (mRespName == "ALICE3") { + mSimRespVDScaleX = o2::trk::constants::alice3resp::pitchX / o2::trk::SegmentationChip::PitchRowVD; + mSimRespVDScaleZ = o2::trk::constants::alice3resp::pitchZ / o2::trk::SegmentationChip::PitchColVD; + mSimRespVDShift = mChipSimRespVD->getDepthMax(); // the curved, rescaled, sensors have a width from 0 to -95 um. Must align the start of epi layer with the response function. + mSimRespMLOTScaleX = o2::trk::constants::alice3resp::pitchX / o2::trk::SegmentationChip::PitchRowMLOT; + mSimRespMLOTScaleZ = o2::trk::constants::alice3resp::pitchZ / o2::trk::SegmentationChip::PitchColMLOT; + } else { + LOG(fatal) << "Unknown response name: " << mRespName; + } + + mSimRespMLOTShift = mChipSimRespMLOT->getDepthMax() - thicknessMLOT / 2.f; // the shift should be done considering the rescaling done to adapt to the wrong silicon thickness. TODO: remove the scaling factor for the depth when the silicon thickness match the simulated response + // importing the parameters from DPLDigitizerParam.h auto& dOptTRK = DPLDigitizerParam::Instance(); LOGP(info, "TRK Digitizer is initialised."); mParams.print(); - LOGP(info, "VD shift = {} ; ML/OT shift = {} = {} - {}", mSimRespVDShift, mSimRespMLOTShift, mChipSimRespMLOT->getDepthMax(), thicknessMLOT * mSimRespMLOTScaleDepth / 2.f); + LOGP(info, "VD shift = {} ; ML/OT shift = {} = {} - {}", mSimRespVDShift, mSimRespMLOTShift, mChipSimRespMLOT->getDepthMax(), thicknessMLOT / 2.f); LOGP(info, "VD pixel scale on x = {} ; z = {}", mSimRespVDScaleX, mSimRespVDScaleZ); LOGP(info, "ML/OT pixel scale on x = {} ; z = {}", mSimRespMLOTScaleX, mSimRespMLOTScaleZ); LOGP(info, "Response orientation: {}", mSimRespOrientation ? "flipped" : "normal"); @@ -92,7 +101,7 @@ void Digitizer::init() mIRFirstSampledTF = o2::raw::HBFUtils::Instance().getFirstSampledTFIR(); } -o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) +const o2::trk::ChipSimResponse* Digitizer::getChipResponse(int chipID) { if (mGeometry->getSubDetID(chipID) == 0) { /// VD return mChipSimRespVD; @@ -105,14 +114,13 @@ 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 entry " << evID << " from source " << srcID - << " at time " << mEventTime << " ROFrame= " << mNewROFrame << ")" - << " cont.mode: " << isContinuous() + << ") hits of event " << evID << " from source " << srcID + << " at time " << mEventTime.getTimeNS() << " ROFrame = " << mNewROFrame << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; std::cout << "Printing segmentation info: " << std::endl; @@ -120,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(); @@ -132,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 "; + 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); - - if (mCollisionTimeWrtROF < 0 && nbc > 0) { - nbc--; - } - - mNewROFrame = nbc / mParams.getROFrameLengthInBC(); + auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF); - LOG(info) << " 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) { @@ -204,10 +208,10 @@ 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); /// TODO: add noise + chip.addNoise(mROFrameMin, mROFrameMin, &mParams, mGeometry->getSubDetID(chip.getChipIndex()), mGeometry->getLayer(chip.getChipIndex())); /// TODO: add noise auto& buffer = chip.getPreDigits(); if (buffer.empty()) { continue; @@ -236,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); } @@ -252,7 +252,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) } //_______________________________________________________________________ -void Digitizer::processHit(const o2::itsmft::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); @@ -272,7 +272,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID return; } float timeInROF = hit.GetTime() * sec2ns; - LOG(debug) << "timeInROF: " << timeInROF; + LOG(debug) << "Hit time: " << timeInROF << " ns"; if (timeInROF > 20e3) { const int maxWarn = 10; static int warnNo = 0; @@ -282,10 +282,8 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID } return; } - if (isContinuous()) { - timeInROF += mCollisionTimeWrtROF; - } - if (timeInROF < 0) { + 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; return; @@ -297,9 +295,9 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID } 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) { @@ -333,17 +331,6 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID // std::cout<< "Example hit in local frame: " << exampleLoc << std::endl; // std::cout<<"Going back to glob coordinates: " << (matrix * exampleLoc) << std::endl; - //// adapting the depth (Y) of the chip to the APTS response maximum depth - LOG(debug) << "local original: startPos = " << xyzLocS << ", endPos = " << xyzLocE << std::endl; - if (subDetID == 0) { - xyzLocS.SetY(xyzLocS.Y() * mSimRespVDScaleDepth); - xyzLocE.SetY(xyzLocE.Y() * mSimRespVDScaleDepth); - } else { - xyzLocS.SetY(xyzLocS.Y() * mSimRespMLOTScaleDepth); - xyzLocE.SetY(xyzLocE.Y() * mSimRespMLOTScaleDepth); - } - LOG(debug) << "rescaled Y: startPos = " << xyzLocS << ", endPos = " << xyzLocE << std::endl; - math_utils::Vector3D step(xyzLocE); step -= xyzLocS; step *= nStepsInv; // position increment at each step @@ -422,7 +409,10 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID // take into account that the ChipSimResponse depth defintion has different min/max boundaries // although the max should coincide with the surface of the epitaxial layer, which in the chip // local coordinates has Y = +SensorLayerThickness/2 + // LOG(info)<<"SubdetID = " << subDetID<< " shift: "<print(); // print the response matrix for debugging for (int irow = AlpideRespSimMat::NPix; irow--;) { @@ -487,7 +467,7 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID // fire the pixels assuming Poisson(n_response_electrons) o2::MCCompLabel lbl(hit.GetTrackID(), evID, srcID, false); auto roFrameAbs = mNewROFrame + roFrameRel; - LOG(debug) << "Spanning through rows and columns; rowspan = " << rowSpan << " colspan = " << colSpan << " = " << colE << " - " << colS << " +1 " << std::endl; + LOG(debug) << "\nSpanning through rows and columns; rowspan = " << rowSpan << " colspan = " << colSpan << " = " << colE << " - " << colS << " +1 "; for (int irow = rowSpan; irow--;) { // irow ranging from 4 to 0 uint16_t rowIS = irow + rowS; // row distant irow from the row of the hit start for (int icol = colSpan; icol--;) { // icol ranging from 4 to 0 @@ -495,9 +475,9 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID if (nEleResp <= 1.e-36) { continue; } - LOG(debug) << "nEleResp: value " << nEleResp << " for pixel " << irow << " " << icol << std::endl; + LOG(debug) << "nEleResp: value " << nEleResp << " for pixel " << irow << " " << icol; int nEle = gRandom->Poisson(nElectrons * nEleResp); // total charge in given pixel = number of electrons generated in the hit multiplied by the probability of being detected in their position - LOG(debug) << "Charge detected in the pixel: " << nEle << " for pixel " << irow << " " << icol << std::endl; + LOG(debug) << "Charge detected in the pixel: " << nEle << " for pixel " << irow << " " << icol; // ignore charge which have no chance to fire the pixel if (nEle < mParams.getMinChargeToAccount()) { /// TODO: substitute with the threshold? LOG(debug) << "Ignoring pixel with nEle = " << nEle << " < min charge to account " @@ -512,26 +492,25 @@ void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID 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++) { + 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? @@ -547,8 +526,9 @@ void Digitizer::registerDigits(o2::trk::ChipDigitsContainer& chip, uint32_t roFr o2::itsmft::PreDigit* pd = chip.findDigit(key); if (!pd) { chip.addDigit(key, roFr, row, col, nEleROF, lbl); - LOG(debug) << "Added digit " << key << " " << roFr << " " << row << " " << col << " " << nEleROF; + LOG(debug) << "Added digit with key: " << key << " ROF: " << roFr << " row: " << row << " col: " << col << " charge: " << nEleROF; } else { // there is already a digit at this slot, account as PreDigitExtra contribution + LOG(debug) << "Added to pre-digit with key: " << key << " ROF: " << roFr << " row: " << row << " col: " << col << " charge: " << nEleROF; pd->charge += nEleROF; if (pd->labelRef.label == lbl) { // don't store the same label twice continue; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx new file mode 100644 index 0000000000000..1f49b84114b9d --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/Hit.cxx @@ -0,0 +1,15 @@ +// 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 Hit.cxx +/// \brief Implementation of the Hit class + +#include "TRKSimulation/Hit.h" diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx index a95418afbba25..5206985992ecf 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKLayer.cxx @@ -10,228 +10,484 @@ // or submit itself to any jurisdiction. #include "TRKSimulation/TRKLayer.h" -#include "TRKBase/GeometryTGeo.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 { -TRKLayer::TRKLayer(int layerNumber, std::string layerName, float rInn, float rOut, float zLength, float layerX2X0) - : mLayerNumber(layerNumber), mLayerName(layerName), mInnerRadius(rInn), mOuterRadius(rOut), mZ(zLength), mX2X0(layerX2X0), mModuleWidth(4.54), mLayout(kCylinder) +TRKCylindricalLayer::TRKCylindricalLayer(int layerNumber, std::string layerName, float rInn, float length, float thickOrX2X0, MatBudgetParamMode mode) + : mLayerNumber(layerNumber), mLayerName(layerName), mInnerRadius(rInn), mLength(length) { - float Si_X0 = 9.5f; - mChipThickness = mX2X0 * Si_X0; - LOGP(info, "Creating layer: id: {} rInner: {} rOuter: {} zLength: {} x2X0: {}", mLayerNumber, mInnerRadius, mOuterRadius, mZ, mX2X0); + if (mode == MatBudgetParamMode::Thickness) { + mChipThickness = thickOrX2X0; + mX2X0 = thickOrX2X0 / Si_X0; + mOuterRadius = rInn + thickOrX2X0; + } else if (mode == MatBudgetParamMode::X2X0) { + mX2X0 = thickOrX2X0; + mChipThickness = thickOrX2X0 * Si_X0; + mOuterRadius = rInn + thickOrX2X0 * Si_X0; + } + + LOGP(info, "Creating layer: id: {} rInner: {} rOuter: {} zLength: {} x2X0: {}", mLayerNumber, mInnerRadius, mOuterRadius, mLength, mX2X0); } -TRKLayer::TRKLayer(int layerNumber, std::string layerName, float rInn, float zLength, float thick) - : mLayerNumber(layerNumber), mLayerName(layerName), mInnerRadius(rInn), mZ(zLength), mChipThickness(thick), mModuleWidth(4.54), mLayout(kCylinder) +TGeoVolume* TRKCylindricalLayer::createSensor() { - float Si_X0 = 9.5f; - mOuterRadius = rInn + thick; - mX2X0 = mChipThickness / Si_X0; - LOGP(info, "Creating layer: id: {} rInner: {} rOuter: {} zLength: {} x2X0: {}", mLayerNumber, mInnerRadius, mOuterRadius, mZ, mX2X0); -} + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string sensName = GeometryTGeo::getTRKSensorPattern() + std::to_string(mLayerNumber); + TGeoShape* sensor = new TGeoTube(mInnerRadius, mInnerRadius + sSensorThickness, mLength / 2); + TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); + sensVol->SetLineColor(kYellow); -TGeoVolume* TRKLayer::createSensor(std::string type, double width) + return sensVol; +}; + +TGeoVolume* TRKCylindricalLayer::createMetalStack() { TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); - std::string sensName = Form("%s%d", GeometryTGeo::getTRKSensorPattern(), this->mLayerNumber); + std::string metalName = GeometryTGeo::getTRKMetalStackPattern() + std::to_string(mLayerNumber); + TGeoShape* metalStack = new TGeoTube(mInnerRadius + sSensorThickness, mInnerRadius + mChipThickness, mLength / 2); + TGeoVolume* metalVol = new TGeoVolume(metalName.c_str(), metalStack, medSi); + metalVol->SetLineColor(kGray); - TGeoShape* sensor; + return metalVol; +}; - if (type == "cylinder") { - sensor = new TGeoTube(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2); - } else if (type == "flat") { - if (width < 0) { - LOGP(fatal, "Attempting to create sensor with invalid width"); - } - sensor = new TGeoBBox(width / 2, mChipThickness / 2, mZ / 2); - } else { - LOGP(fatal, "Sensor of type '{}' is not implemented", type); - } +void TRKCylindricalLayer::createLayer(TGeoVolume* motherVolume) +{ + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + TGeoTube* layer = new TGeoTube(mInnerRadius, mInnerRadius + mChipThickness, mLength / 2); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow); + TGeoVolume* sensVol = createSensor(); + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), layerVol->GetName()); + layerVol->AddNode(sensVol, 1, nullptr); + + TGeoVolume* metalVol = createMetalStack(); + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), layerVol->GetName()); + layerVol->AddNode(metalVol, 1, nullptr); + + LOGP(debug, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); + motherVolume->AddNode(layerVol, 1, nullptr); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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() +{ + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string sensName = GeometryTGeo::getTRKSensorPattern() + std::to_string(mLayerNumber); + TGeoShape* sensor = new TGeoBBox((sChipWidth - sDeadzoneWidth) / 2, sSensorThickness / 2, sChipLength / 2); TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); sensVol->SetLineColor(kYellow); return sensVol; -}; +} -TGeoVolume* TRKLayer::createChip(std::string type, double width) +TGeoVolume* TRKSegmentedLayer::createDeadzone() { TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); - std::string chipName = o2::trk::GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber); + std::string deadName = GeometryTGeo::getTRKDeadzonePattern() + std::to_string(mLayerNumber); + TGeoShape* deadzone = new TGeoBBox(sDeadzoneWidth / 2, sSensorThickness / 2, sChipLength / 2); + TGeoVolume* deadVol = new TGeoVolume(deadName.c_str(), deadzone, medSi); + deadVol->SetLineColor(kGray); - TGeoShape* chip; - TGeoVolume* sensVol; + return deadVol; +} - if (type == "cylinder") { - chip = new TGeoTube(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2); - sensVol = createSensor("cylinder"); - } else if (type == "flat") { - if (width < 0) { - LOGP(fatal, "Attempting to create chip with invalid width"); - } - chip = new TGeoBBox(width / 2, mChipThickness / 2, mZ / 2); - sensVol = createSensor("flat", width); - } else { - LOGP(fatal, "Sensor of type '{}' is not implemented", type); - } +TGeoVolume* TRKSegmentedLayer::createMetalStack() +{ + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string metalName = GeometryTGeo::getTRKMetalStackPattern() + std::to_string(mLayerNumber); + TGeoShape* metalStack = new TGeoBBox(sChipWidth / 2, (mChipThickness - sSensorThickness) / 2, sChipLength / 2); + TGeoVolume* metalVol = new TGeoVolume(metalName.c_str(), metalStack, medSi); + metalVol->SetLineColor(kGray); + + return metalVol; +} +TGeoVolume* TRKSegmentedLayer::createChip() +{ + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string chipName = GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber); + TGeoShape* chip = new TGeoBBox(sChipWidth / 2, mChipThickness / 2, sChipLength / 2); TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); - LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, nullptr); chipVol->SetLineColor(kYellow); + TGeoVolume* sensVol = createSensor(); + TGeoCombiTrans* transSens = new TGeoCombiTrans(); + + TGeoVolume* deadVol = createDeadzone(); + TGeoCombiTrans* transDead = new TGeoCombiTrans(); + + TGeoVolume* metalVol = createMetalStack(); + TGeoCombiTrans* transMetal = new TGeoCombiTrans(); + + 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); + return chipVol; } -TGeoVolume* TRKLayer::createStave(std::string type, double width) +TGeoVolume* TRKSegmentedLayer::createModule() { - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - std::string staveName = o2::trk::GeometryTGeo::getTRKStavePattern() + std::to_string(mLayerNumber); - - TGeoShape* stave; - TGeoVolume* staveVol; - TGeoVolume* chipVol; - - if (type == "cylinder") { - stave = new TGeoTube(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2); - chipVol = createChip("cylinder"); - staveVol = new TGeoVolume(staveName.c_str(), stave, medAir); - LOGP(info, "Inserting {} in {} ", chipVol->GetName(), staveVol->GetName()); - staveVol->AddNode(chipVol, 1, nullptr); - } else if (type == "flat") { - if (width < 0) { - LOGP(fatal, "Attempting to create stave with invalid width"); - } - stave = new TGeoBBox(width / 2, mChipThickness / 2, mZ / 2); - chipVol = createChip("flat", width); - staveVol = new TGeoVolume(staveName.c_str(), stave, medAir); - LOGP(info, "Inserting {} in {} ", chipVol->GetName(), staveVol->GetName()); - staveVol->AddNode(chipVol, 1, nullptr); - } else if (type == "staggered") { - double width = mModuleWidth * 2; // Each stave has two modules (based on the LOI design) - stave = new TGeoBBox(width / 2, mLogicalVolumeThickness / 2, mZ / 2); - TGeoVolume* chipVolLeft = createChip("flat", mModuleWidth); - TGeoVolume* chipVolRight = createChip("flat", mModuleWidth); - staveVol = new TGeoVolume(staveName.c_str(), stave, medAir); - + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string moduleName = GeometryTGeo::getTRKModulePattern() + std::to_string(mLayerNumber); + TGeoShape* module = new TGeoBBox(sModuleWidth / 2, mChipThickness / 2, sModuleLength / 2); + TGeoVolume* moduleVol = new TGeoVolume(moduleName.c_str(), module, medSi); + moduleVol->SetLineColor(kYellow); + + for (int iChip = 0; iChip < sHalfNumberOfChips; iChip++) { + TGeoVolume* chipVolLeft = createChip(); + double xLeft = -sModuleWidth / 2 + constants::moduleMLOT::gaps::outerEdgeLongSide + constants::moduleMLOT::chip::width / 2; + double zLeft = -sModuleLength / 2 + constants::moduleMLOT::gaps::outerEdgeShortSide + iChip * (constants::moduleMLOT::chip::length + constants::moduleMLOT::gaps::interChips) + constants::moduleMLOT::chip::length / 2; TGeoCombiTrans* transLeft = new TGeoCombiTrans(); - transLeft->SetTranslation(-mModuleWidth / 2 + 0.05, 0, 0); // 1mm overlap between the modules - LOGP(info, "Inserting {} in {} ", chipVolLeft->GetName(), staveVol->GetName()); - staveVol->AddNode(chipVolLeft, 0, transLeft); - + transLeft->SetTranslation(xLeft, 0, zLeft); + TGeoRotation* rot = new TGeoRotation(); + rot->RotateY(180); + transLeft->SetRotation(rot); + LOGP(debug, "Inserting {} in {} ", chipVolLeft->GetName(), moduleVol->GetName()); + moduleVol->AddNode(chipVolLeft, iChip * 2, transLeft); + + TGeoVolume* chipVolRight = createChip(); + double xRight = +sModuleWidth / 2 - constants::moduleMLOT::gaps::outerEdgeLongSide - constants::moduleMLOT::chip::width / 2; + double zRight = -sModuleLength / 2 + constants::moduleMLOT::gaps::outerEdgeShortSide + iChip * (constants::moduleMLOT::chip::length + constants::moduleMLOT::gaps::interChips) + constants::moduleMLOT::chip::length / 2; TGeoCombiTrans* transRight = new TGeoCombiTrans(); - transRight->SetTranslation(mModuleWidth / 2 - 0.05, 0.2, 0); - LOGP(info, "Inserting {} in {} ", chipVolRight->GetName(), staveVol->GetName()); - staveVol->AddNode(chipVolRight, 1, transRight); + transRight->SetTranslation(xRight, 0, zRight); + LOGP(debug, "Inserting {} in {} ", chipVolRight->GetName(), moduleVol->GetName()); + moduleVol->AddNode(chipVolRight, iChip * 2 + 1, transRight); + } + + 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 { - LOGP(fatal, "Chip of type '{}' is not implemented", type); + // 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, 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() +{ + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + std::string staveName = GeometryTGeo::getTRKStavePattern() + std::to_string(mLayerNumber); + TGeoShape* stave = new TGeoBBox(sStaveWidth / 2, mChipThickness / 2, mLength / 2); + TGeoVolume* staveVol = new TGeoVolume(staveName.c_str(), stave, medAir); staveVol->SetLineColor(kYellow); + for (int iModule = 0; iModule < mNumberOfModules; iModule++) { + TGeoVolume* moduleVol = createModule(); + double zPos = -0.5 * mNumberOfModules * sModuleLength + (iModule + 0.5) * sModuleLength; + TGeoCombiTrans* trans = new TGeoCombiTrans(); + trans->SetTranslation(0, 0, zPos); + LOGP(debug, "Inserting {} in {} ", moduleVol->GetName(), staveVol->GetName()); + staveVol->AddNode(moduleVol, iModule, trans); + } + return staveVol; } -void TRKLayer::createLayer(TGeoVolume* motherVolume) +void TRKMLLayer::createLayer(TGeoVolume* motherVolume) { - TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + // 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(rMin, rMax, mLength / 2); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow); - std::string staveName = o2::trk::GeometryTGeo::getTRKStavePattern() + std::to_string(mLayerNumber), - chipName = o2::trk::GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber), - sensName = Form("%s%d", GeometryTGeo::getTRKSensorPattern(), mLayerNumber); + // Compute the 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() / 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", mNumberOfStaves, overlap * 10); + + for (int iStave = 0; iStave < mNumberOfStaves; iStave++) { + TGeoVolume* staveVol = createStave(); + TGeoCombiTrans* trans = new TGeoCombiTrans(); + // 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); + // 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); + } - double layerThickness = mChipThickness; - if (mLayout != eLayout::kCylinder) { - layerThickness = mLogicalVolumeThickness; + LOGP(debug, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); + 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}; } - TGeoTube* layer = new TGeoTube(mInnerRadius - 0.333 * layerThickness, mInnerRadius + 0.667 * layerThickness, mZ / 2); - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); - layerVol->SetLineColor(kYellow); + /*// 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; - if (mLayout == eLayout::kCylinder) { - auto staveVol = createStave("cylinder"); - LOGP(info, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); - layerVol->AddNode(staveVol, 1, nullptr); - } else if (mLayout == eLayout::kTurboStaves) { - // Compute the number of staves - double width = mModuleWidth; // Each stave has two modules (based on the LOI design) - if (mInnerRadius > 25) { - width *= 2; // Outer layers have two modules per stave - } + 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); - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / width); - nStaves += nStaves % 2; // Require an even number of staves - - // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; - double theta1 = std::atan(width / 2 / mInnerRadius); - double st = std::sin(theta); - double ct = std::cos(theta); - double theta2 = std::atan((mInnerRadius * st - width / 2 * ct) / (mInnerRadius * ct + width / 2 * st)); - double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); - - for (int iStave = 0; iStave < nStaves; iStave++) { - TGeoVolume* staveVol = createStave("flat", width); - - // Put the staves in the correct position and orientation - TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - TGeoRotation* rot = new TGeoRotation("rot", theta + 90 + 3, 0, 0); - trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); - - LOGP(info, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); - layerVol->AddNode(staveVol, iStave, trans); + 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); } - } else if (mLayout == kStaggered) { - // Compute the number of staves - double width = mModuleWidth * 2; // Each stave has two modules (based on the LOI design) - int nStaves = (int)std::ceil(mInnerRadius * 2 * TMath::Pi() / width); - nStaves += nStaves % 2; // Require an even number of staves - - // Compute the size of the overlap region - double theta = 2 * TMath::Pi() / nStaves; - double theta1 = std::atan(width / 2 / mInnerRadius); - double st = std::sin(theta); - double ct = std::cos(theta); - double theta2 = std::atan((mInnerRadius * st - width / 2 * ct) / (mInnerRadius * ct + width / 2 * st)); - double overlap = (theta1 - theta2) * mInnerRadius; - LOGP(info, "Creating a layer with {} staves and {} mm overlap", nStaves, overlap * 10); - - for (int iStave = 0; iStave < nStaves; iStave++) { - TGeoVolume* staveVol = createStave("staggered"); - - // Put the staves in the correct position and orientation - TGeoCombiTrans* trans = new TGeoCombiTrans(); - double theta = 360. * iStave / nStaves; - TGeoRotation* rot = new TGeoRotation("rot", theta + 90, 0, 0); - trans->SetRotation(rot); - trans->SetTranslation(mInnerRadius * std::cos(2. * TMath::Pi() * iStave / nStaves), mInnerRadius * std::sin(2 * TMath::Pi() * iStave / nStaves), 0); - - LOGP(info, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); - layerVol->AddNode(staveVol, iStave, trans); + + return {newRadiusMin - precisionMargin, defaultRadiusMax}; + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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) +{ +} + +TGeoVolume* TRKOTLayer::createHalfStave() +{ + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + std::string halfStaveName = GeometryTGeo::getTRKHalfStavePattern() + std::to_string(mLayerNumber); + 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); + + 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 * nModulesPerHalfBarrel * sModuleLength + (iModule + 0.5) * sModuleLength; + TGeoCombiTrans* trans = new TGeoCombiTrans(); + trans->SetTranslation(0, 0, zPos); + LOGP(debug, "Inserting {} in {} ", moduleVol->GetName(), halfStaveVol->GetName()); + halfStaveVol->AddNode(moduleVol, iModule, trans); + } + + return halfStaveVol; +} + +TGeoVolume* TRKOTLayer::createStave() +{ + std::string staveName = GeometryTGeo::getTRKStavePattern() + std::to_string(mLayerNumber); + TGeoVolume* staveVol = new TGeoVolumeAssembly(staveName.c_str()); + + TGeoVolume* halfStaveVolLeft = createHalfStave(); + TGeoCombiTrans* transLeft = new TGeoCombiTrans(); + transLeft->SetTranslation(-(sHalfStaveWidth - sInStaveOverlap) / 2, 0, 0); + LOGP(debug, "Inserting {} in {} ", halfStaveVolLeft->GetName(), staveVol->GetName()); + staveVol->AddNode(halfStaveVolLeft, 0, transLeft); + + TGeoVolume* halfStaveVolRight = createHalfStave(); + TGeoCombiTrans* transRight = new TGeoCombiTrans(); + transRight->SetTranslation((sHalfStaveWidth - sInStaveOverlap) / 2, 0.2, 0); + LOGP(debug, "Inserting {} in {} ", halfStaveVolRight->GetName(), staveVol->GetName()); + staveVol->AddNode(halfStaveVolRight, 1, transRight); + + return staveVol; +} + +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(rMin, rMax, (mLength + sGapBetweenOuterTrackerBarrelHalves) / 2); + TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), layer, medAir); + layerVol->SetLineColor(kYellow); + + // Compute the 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() / 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 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 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 } - } else { - LOGP(fatal, "Layout not implemented"); + 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(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); } - LOGP(info, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); + + 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/TRKPetalCase.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalCase.cxx deleted file mode 100644 index c729d7d1ec4dd..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalCase.cxx +++ /dev/null @@ -1,202 +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 "TRKSimulation/TRKPetalCase.h" -#include "TRKBase/GeometryTGeo.h" -#include - -#include "Framework/Logger.h" - -#include "TGeoTube.h" -#include "TGeoMatrix.h" -#include "TGeoCompositeShape.h" -#include "TGeoVolume.h" -#include "TString.h" -#include "TMath.h" - -namespace o2 -{ -namespace trk -{ -TRKPetalCase::TRKPetalCase(Int_t number, TGeoVolume* motherVolume, Bool_t irisOpen) : mPetalCaseNumber(number), mOpenState(irisOpen) -{ - - mWallThickness = .15e-1; // cm // Assume all the walls have the same thickness for now. - mRIn = 0.48; // cm - mROut = 3; // cm - mRInOpenState = 1.5; // cm - mPetalCaseLength = 70.; // cm - - // Calculate angular coverages of azimuthal part of wall (equivalent to that of the sensitive volumes) - mAngularCoverageAzimuthalWall = (0.25 * (2 * mRIn * TMath::Pi()) - 2 * mWallThickness) / mRIn; - mAngularCoverageRadialWall = mWallThickness / mRIn; - mToDeg = 180 / TMath::Pi(); - - // Calculate the center of the petal (x_c, y_c, z_c) based on whether it is open or not - mZPos = 0; - if (mOpenState) { - Double_t rHalfPetal = 0.5 * (mRIn + mROut); - Double_t rOpenStateCenter = TMath::Sqrt(rHalfPetal * rHalfPetal + mRInOpenState * mRInOpenState); - mXPos = rOpenStateCenter * TMath::Cos(0.25 * TMath::Pi() + (mPetalCaseNumber - 1) * 0.5 * TMath::Pi()); - mYPos = rOpenStateCenter * TMath::Sin(0.25 * TMath::Pi() + (mPetalCaseNumber - 1) * 0.5 * TMath::Pi()); - } else { - mXPos = 0.; - mYPos = 0.; - } - - // Make the petal case - constructCase(motherVolume); - // Make coldplate - constructColdPlate(motherVolume); - // Add the detection petals (quarter disks and barrel layers) - constructDetectionPetals(motherVolume); -} - -TString TRKPetalCase::getFullName() -{ - TString fullCompositeName = Form("PETALCASE%d_FULLCOMPOSITE", mPetalCaseNumber); - TGeoCompositeShape* fullCompositeShape = new TGeoCompositeShape(fullCompositeName, mFullCompositeFormula); - return fullCompositeName; -} - -void TRKPetalCase::constructCase(TGeoVolume* motherVolume) -{ - - // Petal case parts in TGeoTubeSeg - mInnerAzimuthalWall = new TGeoTubeSeg(Form("PETAL%d_INNER_AZIMUTHAL_WALL", mPetalCaseNumber), mRIn, mRIn + mWallThickness, mPetalCaseLength / 2., -0.5 * mAngularCoverageAzimuthalWall * mToDeg, 0.5 * mAngularCoverageAzimuthalWall * mToDeg); - mOuterAzimuthalWall = new TGeoTubeSeg(Form("PETAL%d_OUTER_AZIMUTHAL_WALL", mPetalCaseNumber), mROut, mROut + mWallThickness, mPetalCaseLength / 2., -0.5 * mAngularCoverageAzimuthalWall * mToDeg, 0.5 * mAngularCoverageAzimuthalWall * mToDeg); - mRadialWall = new TGeoTubeSeg(Form("PETAL%d_RADIAL_WALL", mPetalCaseNumber), mRIn, mROut + mWallThickness, mPetalCaseLength / 2., -0.5 * mAngularCoverageRadialWall * mToDeg, 0.5 * mAngularCoverageRadialWall * mToDeg); - mForwardWall = new TGeoTubeSeg(Form("PETAL%d_FORWARD_WALL", mPetalCaseNumber), mRIn, mROut + mWallThickness, mWallThickness / 2., -0.5 * (mAngularCoverageAzimuthalWall + 2 * mAngularCoverageRadialWall) * mToDeg, 0.5 * (mAngularCoverageAzimuthalWall + 2 * mAngularCoverageRadialWall) * mToDeg); - - // Rotate to correct section : 0-3 - mAzimuthalWallRot = new TGeoRotation((TString)Form("PETAL%d_AZIMUTHAL_WALL_ROT", mPetalCaseNumber), (mPetalCaseNumber * 0.5 * TMath::Pi() + 0.5 * mAngularCoverageAzimuthalWall + mAngularCoverageRadialWall) * mToDeg, 0., 0.); - mAzimuthalWallRot->RegisterYourself(); - mRadialWall1Rot = new TGeoRotation((TString)Form("PETAL%d_RADIAL_WALL1_ROT", mPetalCaseNumber), (mPetalCaseNumber * 0.5 * TMath::Pi() + 0.5 * mAngularCoverageRadialWall) * mToDeg, 0., 0.); - mRadialWall1Rot->RegisterYourself(); - mRadialWall2Rot = new TGeoRotation((TString)Form("PETAL%d_RADIAL_WALL2_ROT", mPetalCaseNumber), (mPetalCaseNumber * 0.5 * TMath::Pi() + mAngularCoverageAzimuthalWall + 1.5 * mAngularCoverageRadialWall) * mToDeg, 0., 0.); - mRadialWall2Rot->RegisterYourself(); - - // Place to correct position (open or closed) - mAzimuthalWallCombiTrans = new TGeoCombiTrans((TString)Form("PETAL%d_AZIMUTHAL_WALL_COMBITRANS", mPetalCaseNumber), mXPos, mYPos, mZPos, mAzimuthalWallRot); - mAzimuthalWallCombiTrans->RegisterYourself(); - mRadialWall1CombiTrans = new TGeoCombiTrans((TString)Form("PETAL%d_RADIAL_WALL1_COMBITRANS", mPetalCaseNumber), mXPos, mYPos, mZPos, mRadialWall1Rot); - mRadialWall1CombiTrans->RegisterYourself(); - mRadialWall2CombiTrans = new TGeoCombiTrans((TString)Form("PETAL%d_RADIAL_WALL2_COMBITRANS", mPetalCaseNumber), mXPos, mYPos, mZPos, mRadialWall2Rot); - mRadialWall2CombiTrans->RegisterYourself(); - mForwardWall1CombiTrans = new TGeoCombiTrans((TString)Form("PETAL%d_FORWARD_WALL1_COMBITRANS", mPetalCaseNumber), mXPos, mYPos, (mPetalCaseLength + mWallThickness) / 2., mAzimuthalWallRot); - mForwardWall1CombiTrans->RegisterYourself(); - mForwardWall2CombiTrans = new TGeoCombiTrans((TString)Form("PETAL%d_FORWARD_WALL2_COMBITRANS", mPetalCaseNumber), mXPos, mYPos, -(mPetalCaseLength + mWallThickness) / 2., mAzimuthalWallRot); - mForwardWall2CombiTrans->RegisterYourself(); - - TString petalCaseCompositeFormula = (TString)Form("PETAL%d_INNER_AZIMUTHAL_WALL:PETAL%d_AZIMUTHAL_WALL_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber) + (TString)Form("+PETAL%d_OUTER_AZIMUTHAL_WALL:PETAL%d_AZIMUTHAL_WALL_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber) + (TString)Form("+PETAL%d_RADIAL_WALL:PETAL%d_RADIAL_WALL1_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber) + (TString)Form("+PETAL%d_RADIAL_WALL:PETAL%d_RADIAL_WALL2_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber) + (TString)Form("+PETAL%d_FORWARD_WALL:PETAL%d_FORWARD_WALL1_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber) + (TString)Form("+PETAL%d_FORWARD_WALL:PETAL%d_FORWARD_WALL2_COMBITRANS", mPetalCaseNumber, mPetalCaseNumber); - - TGeoCompositeShape* petalCaseComposite = new TGeoCompositeShape((TString)Form("PETALCASE%dsh", mPetalCaseNumber), petalCaseCompositeFormula); - mFullCompositeFormula = petalCaseComposite->GetName(); - auto& matmgr = o2::base::MaterialManager::Instance(); - const TGeoMedium* kMedBe = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_BERYLLIUM"); - - mPetalCaseName = Form("PETALCASE%d", mPetalCaseNumber); - mPetalCaseVolume = new TGeoVolume(mPetalCaseName, petalCaseComposite, kMedBe); - mPetalCaseVolume->SetVisibility(1); - mPetalCaseVolume->SetLineColor(kGray); - - LOGP(info, "Creating IRIS Tracker vacuum petal case {}", mPetalCaseNumber); - LOGP(info, "Inserting {} in {} ", mPetalCaseVolume->GetName(), motherVolume->GetName()); - motherVolume->AddNode(mPetalCaseVolume, 1, nullptr); -} - -void TRKPetalCase::constructColdPlate(TGeoVolume* motherVolume) -{ - Double_t coldPlateRadius = 2.6; // cm - Double_t coldPlateThickness = 0.15; // cm - Double_t coldPlateLength = 50.; // cm - - mColdPlate = new TGeoTubeSeg((TString)Form("PETAL%d_COLDPLATE", mPetalCaseNumber), coldPlateRadius, coldPlateRadius + coldPlateThickness, coldPlateLength / 2., -0.5 * mAngularCoverageAzimuthalWall * mToDeg, 0.5 * mAngularCoverageAzimuthalWall * mToDeg); - auto& matmgr = o2::base::MaterialManager::Instance(); - const TGeoMedium* medCeramic = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_CERAMIC"); - mColdPlateVolume = new TGeoVolume(Form("COLDPLATE%d", mPetalCaseNumber), mColdPlate, medCeramic); - - TString coldPlateCompositeFormula = mColdPlate->GetName(); - coldPlateCompositeFormula += ":"; - coldPlateCompositeFormula += mAzimuthalWallCombiTrans->GetName(); - addToPetalCaseComposite(coldPlateCompositeFormula); - - mColdPlateVolume->SetVisibility(1); - mColdPlateVolume->SetLineColor(kGray); - - LOGP(info, "Creating cold plate service"); - LOGP(info, "Inserting {} in {} ", mColdPlateVolume->GetName(), motherVolume->GetName()); - motherVolume->AddNode(mColdPlateVolume, 1, mAzimuthalWallCombiTrans); -} - -void TRKPetalCase::constructDetectionPetals(TGeoVolume* motherVolume) -{ - // Add petal layers - // layerNumber, layerName, rIn, angularCoverage, zLength, layerx2X0 - mPetalLayers.emplace_back(0, Form("%s_LAYER%d", mPetalCaseName.Data(), 0), 0.5f, mAngularCoverageAzimuthalWall, 50.f, 1.e-3); - mPetalLayers.emplace_back(1, Form("%s_LAYER%d", mPetalCaseName.Data(), 1), 1.2f, mAngularCoverageAzimuthalWall, 50.f, 1.e-3); - mPetalLayers.emplace_back(2, Form("%s_LAYER%d", mPetalCaseName.Data(), 2), 2.5f, mAngularCoverageAzimuthalWall, 50.f, 1.e-3); - for (Int_t i = 0; i < mPetalLayers.size(); ++i) { - mPetalLayers[i].createLayer(motherVolume, mAzimuthalWallCombiTrans); - } - - // Add petal disks - // diskNumber, diskName, zPos, rIn, rOut, angularCoverage, diskx2X0 - mPetalDisks.emplace_back(0, Form("%s_DISK%d", mPetalCaseName.Data(), 0), 26., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - mPetalDisks.emplace_back(1, Form("%s_DISK%d", mPetalCaseName.Data(), 1), 30., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - mPetalDisks.emplace_back(2, Form("%s_DISK%d", mPetalCaseName.Data(), 2), 34., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - mPetalDisks.emplace_back(3, Form("%s_DISK%d", mPetalCaseName.Data(), 3), -26., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - mPetalDisks.emplace_back(4, Form("%s_DISK%d", mPetalCaseName.Data(), 4), -30., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - mPetalDisks.emplace_back(5, Form("%s_DISK%d", mPetalCaseName.Data(), 5), -34., .5, 2.5, mAngularCoverageAzimuthalWall, 1.e-3); - for (Int_t i = 0; i < mPetalDisks.size(); ++i) { - mPetalDisks[i].createDisk(motherVolume, mAzimuthalWallCombiTrans); - } - - addDetectionPetelsToFullComposite(); -} - -void TRKPetalCase::addDetectionPetelsToFullComposite() -{ - for (Int_t i = 0; i < mPetalLayers.size(); ++i) { - Double_t zLength = mPetalLayers[i].getZLength(); - Double_t rIn = mPetalLayers[i].getInnerRadius(); - Double_t thickness = mPetalLayers[i].getChipThickness(); - Double_t angularCoverage = mPetalLayers[i].getAngularCoverage(); - TGeoTubeSeg* layerForExcavation = new TGeoTubeSeg(Form("PETALCASE%d_EXCAVATIONLAYER%d", mPetalCaseNumber, i), rIn, rIn + thickness, zLength / 2., -0.5 * angularCoverage * mToDeg, 0.5 * angularCoverage * mToDeg); - - TString layerForExcavationCompositeFormula = layerForExcavation->GetName(); - layerForExcavationCompositeFormula += ":"; - layerForExcavationCompositeFormula += mAzimuthalWallCombiTrans->GetName(); - addToPetalCaseComposite(layerForExcavationCompositeFormula); - } - - for (Int_t i = 0; i < mPetalDisks.size(); ++i) { - Double_t zPos = mPetalDisks[i].getZ(); - Double_t rIn = mPetalDisks[i].getInnerRadius(); - Double_t rOut = mPetalDisks[i].getOuterRadius(); - Double_t thickness = mPetalDisks[i].getThickness(); - Double_t angularCoverage = mPetalDisks[i].getAngularCoverage(); - TGeoTubeSeg* diskForExcavation = new TGeoTubeSeg(Form("PETALCASE%d_EXCAVATIONDISK%d", mPetalCaseNumber, i), rIn, rOut, thickness / 2., -0.5 * angularCoverage * mToDeg, 0.5 * angularCoverage * mToDeg); - TGeoCombiTrans* diskForExcavationCombiTrans = new TGeoCombiTrans(*(mAzimuthalWallCombiTrans->MakeClone())); // Copy from petal case - diskForExcavationCombiTrans->SetName((TString)Form("PETALCASE%d_EXCAVATIONDISK%d_COMBITRANS", mPetalCaseNumber, i)); - diskForExcavationCombiTrans->SetDz(zPos); // Overwrite z location - diskForExcavationCombiTrans->RegisterYourself(); - - TString diskForExcavationCompositeFormula = diskForExcavation->GetName(); - diskForExcavationCompositeFormula += ":"; - diskForExcavationCompositeFormula += diskForExcavationCombiTrans->GetName(); - addToPetalCaseComposite(diskForExcavationCompositeFormula); - } -} - -// ClassImp(TRKPetalCase); -} // namespace trk -} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalDisk.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalDisk.cxx deleted file mode 100644 index e24b24b48c882..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalDisk.cxx +++ /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. - -/// \file TRKPetalDisk.cxx -/// \brief Implementation of the TRKPetalDisk class - -#include "TRKSimulation/TRKPetalDisk.h" -#include "TRKBase/GeometryTGeo.h" - -#include // for LOG - -#include "TGeoManager.h" // for TGeoManager, gGeoManager -#include "TGeoMatrix.h" // for TGeoCombiTrans, TGeoRotation, etc -#include "TGeoTube.h" // for TGeoTube, TGeoTubeSeg -#include "TGeoVolume.h" // for TGeoVolume, TGeoVolumeAssembly -#include "TGeoCompositeShape.h" // for TGeoCompositeShape -#include "TMathBase.h" // for Abs -#include "TMath.h" // for Sin, RadToDeg, DegToRad, Cos, Tan, etc -#include "TGeoTube.h" - -#include // for snprintf - -namespace o2 -{ -namespace trk -{ - -TRKPetalDisk::TRKPetalDisk(Int_t diskNumber, std::string diskName, Float_t z, Float_t rIn, Float_t rOut, Float_t angularCoverage, Float_t Diskx2X0) -{ - // Creates a simple parametrized petal disk - mDiskNumber = diskNumber; - mDiskName = diskName; - mZ = z; - mAngularCoverage = angularCoverage; - mx2X0 = Diskx2X0; - mInnerRadius = rIn; - mOuterRadius = rOut; - Float_t Si_X0 = 9.5; - mChipThickness = Diskx2X0 * Si_X0; - - LOG(info) << "Creating TRK Disk " << mDiskNumber; - LOG(info) << " Using silicon X0 = " << Si_X0 << " to emulate disk radiation length."; - LOG(info) << " Disk z = " << mZ << " ; R_in = " << mInnerRadius << " ; R_out = " << mOuterRadius << " ; x2X0 = " << mx2X0 << " ; ChipThickness = " << mChipThickness; -} - -void TRKPetalDisk::createDisk(TGeoVolume* motherVolume, TGeoCombiTrans* combiTrans) -{ - // Create tube, set sensitive volume, add to mother volume - Double_t toDeg = 180 / TMath::Pi(); - std::string chipName = mDiskName + "_" + o2::trk::GeometryTGeo::getTRKChipPattern() + std::to_string(mDiskNumber), - sensName = mDiskName + "_" + Form("%s%d", GeometryTGeo::getTRKSensorPattern(), mDiskNumber); - - mSensorName = sensName; - - TGeoTubeSeg* sensor = new TGeoTubeSeg(mInnerRadius, mOuterRadius, mChipThickness / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - TGeoTubeSeg* chip = new TGeoTubeSeg(mInnerRadius, mOuterRadius, mChipThickness / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - TGeoTubeSeg* disk = new TGeoTubeSeg(mInnerRadius, mOuterRadius, mChipThickness / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - - TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - - TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); - sensVol->SetLineColor(kYellow); - TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); - chipVol->SetLineColor(kYellow); - TGeoVolume* diskVol = new TGeoVolume(mDiskName.c_str(), disk, medAir); - diskVol->SetLineColor(kYellow); - - LOG(info) << "Inserting " << sensVol->GetName() << " inside " << chipVol->GetName(); - chipVol->AddNode(sensVol, 1, nullptr); - - LOG(info) << "Inserting " << chipVol->GetName() << " inside " << diskVol->GetName(); - diskVol->AddNode(chipVol, 1, nullptr); - - // Finally put everything in the mother volume - TGeoCombiTrans* fwdPetalCombiTrans = new TGeoCombiTrans(*(combiTrans->MakeClone())); // Copy from petal case - fwdPetalCombiTrans->SetDz(mZ); // Overwrite z location - fwdPetalCombiTrans->RegisterYourself(); - - LOG(info) << "Inserting " << diskVol->GetName() << " inside " << motherVolume->GetName(); - motherVolume->AddNode(diskVol, 1, fwdPetalCombiTrans); -} -// ClassImp(TRKPetalLayer); - -} // namespace trk -} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalLayer.cxx deleted file mode 100644 index c8ff0d957bb19..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKPetalLayer.cxx +++ /dev/null @@ -1,79 +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 "TRKSimulation/TRKPetalLayer.h" -#include "TRKBase/GeometryTGeo.h" - -#include "Framework/Logger.h" - -#include "TGeoTube.h" -#include "TGeoBBox.h" -#include "TGeoVolume.h" -#include "TGeoTube.h" -#include "TGeoMatrix.h" - -#include "TMath.h" - -namespace o2 -{ -namespace trk -{ -TRKPetalLayer::TRKPetalLayer(Int_t layerNumber, std::string layerName, Float_t rIn, Float_t angularCoverage, Float_t zLength, Float_t layerX2X0) - : mLayerNumber(layerNumber), mLayerName(layerName), mInnerRadius(rIn), mAngularCoverage(angularCoverage), mZ(zLength), mX2X0(layerX2X0), mModuleWidth(4.54) -{ - Float_t Si_X0 = 9.5f; - mChipThickness = mX2X0 * Si_X0; - LOGP(info, "Creating layer: id: {} rInner: {} thickness: {} zLength: {} x2X0: {}", mLayerNumber, mInnerRadius, mChipThickness, mZ, mX2X0); -} - -void TRKPetalLayer::createLayer(TGeoVolume* motherVolume, TGeoCombiTrans* combiTrans) -{ - TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); - TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); - - std::string staveName = mLayerName + "_" + o2::trk::GeometryTGeo::getTRKStavePattern() + std::to_string(mLayerNumber), - chipName = mLayerName + "_" + o2::trk::GeometryTGeo::getTRKChipPattern() + std::to_string(mLayerNumber), - sensName = mLayerName + "_" + Form("%s%d", GeometryTGeo::getTRKSensorPattern(), mLayerNumber); - - mSensorName = sensName; - - Double_t toDeg = 180 / TMath::Pi(); - mLayer = new TGeoTubeSeg(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - TGeoVolume* layerVol = new TGeoVolume(mLayerName.c_str(), mLayer, medAir); - layerVol->SetLineColor(kYellow); - - TGeoTubeSeg* stave = new TGeoTubeSeg(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - TGeoTubeSeg* chip = new TGeoTubeSeg(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - TGeoTubeSeg* sensor = new TGeoTubeSeg(mInnerRadius, mInnerRadius + mChipThickness, mZ / 2., -0.5 * mAngularCoverage * toDeg, 0.5 * mAngularCoverage * toDeg); - - TGeoVolume* sensVol = new TGeoVolume(sensName.c_str(), sensor, medSi); - sensVol->SetLineColor(kYellow); - TGeoVolume* chipVol = new TGeoVolume(chipName.c_str(), chip, medSi); - chipVol->SetLineColor(kYellow); - TGeoVolume* staveVol = new TGeoVolume(staveName.c_str(), stave, medSi); - staveVol->SetLineColor(kYellow); - - LOGP(info, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); - chipVol->AddNode(sensVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", chipVol->GetName(), staveVol->GetName()); - staveVol->AddNode(chipVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", staveVol->GetName(), layerVol->GetName()); - layerVol->AddNode(staveVol, 1, nullptr); - - LOGP(info, "Inserting {} in {} ", layerVol->GetName(), motherVolume->GetName()); - motherVolume->AddNode(layerVol, 1, combiTrans); -} -// ClassImp(TRKPetalLayer); - -} // namespace trk -} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx index 1fb966425f974..53ac0a4b12865 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKServices.cxx @@ -9,19 +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 namespace o2 { @@ -105,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 @@ -124,11 +128,25 @@ 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(); - createMiddleServices(motherVolume); - createOuterDisksServices(motherVolume); - createOuterBarrelServices(motherVolume); + auto& trkPars = TRKBaseParam::Instance(); + if (trkPars.getLayoutSRV() == kLOISymm) { + LOGP(info, "TRK services: LoI version"); + createMiddleServices(vol); + createOuterDisksServices(vol); + createOuterBarrelServices(vol); + } else { + LOGP(info, "TRK services: Peacock layout"); + if (trkPars.includeLowServices) { + createServicesAroundBeamPipe(vol); + } + createMLServicesPeacock(vol); + createOTServicesPeacock(vol); + } } void TRKServices::createVacuumCompositeShape() @@ -173,7 +191,9 @@ void TRKServices::registerVacuum(TGeoVolume* motherVolume) TGeoVolume* vacuumVolume = new TGeoVolume("A3IP_VACUUM", vacuumComposite, kMedVac); // Add the vacuum to the barrel - vacuumVolume->SetLineColor(kGreen - 3); + vacuumVolume->SetLineColor(kAzure + 6); + vacuumVolume->SetTransparency(80); + motherVolume->AddNode(vacuumVolume, 1, new TGeoTranslation(0, 0, 0)); } @@ -262,8 +282,8 @@ void TRKServices::createMiddleServices(TGeoVolume* motherVolume) // 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 = 62.f; - TGeoTube* middleBarrelCarbonSupport = new TGeoTube("TRK_MID_CARBONSUPPORTsh", rMinMiddleCarbonSupport, rMaxMiddleCarbonSupport, zLengthMiddleCarbon); + const float zLengthMiddleCarbon = 129.f; + TGeoTube* middleBarrelCarbonSupport = new TGeoTube("TRK_MID_CARBONSUPPORTsh", rMinMiddleCarbonSupport, rMaxMiddleCarbonSupport, zLengthMiddleCarbon / 2.); TGeoVolume* middleBarrelCarbonSupportVolume = new TGeoVolume("TRK_MID_CARBONSUPPORT", middleBarrelCarbonSupport, medCFiber); middleBarrelCarbonSupportVolume->SetLineColor(kGray); LOGP(info, "Creating carbon fiber support for Middle Tracker"); @@ -316,78 +336,80 @@ void TRKServices::createMiddleServices(TGeoVolume* motherVolume) // Middle barrel connection disks const float rMinMiddleBarrelDisk = 5.68f; const float rMaxMiddleBarrelDisk = 35.f; - const float zLengthMiddleBarrel = 62.f; + const float zLengthMiddleBarrel = 64.5f; for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { - TGeoTube* middleBarrelConnDiskSIO2 = new TGeoTube(Form("TRK_MIDBARCONN_DISK_SIO2sh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, siO2FiberThick); - TGeoTube* middleBarrelConnDiskPE = new TGeoTube(Form("TRK_MIDBARCONN_DISK_PEsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, peFiberThick); - TGeoVolume* middleBarrelConnDiskSIO2Volume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_SIO2_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskSIO2, medSiO2); - TGeoVolume* middleBarrelConnDiskPEVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_PE_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskPE, medPE); + TGeoTube* middleBarrelConnDiskSIO2 = new TGeoTube(Form("TRK_MIDBARCONN_DISK_FIBER_SIO2sh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, siO2FiberThick / 2.); + TGeoTube* middleBarrelConnDiskPE = new TGeoTube(Form("TRK_MIDBARCONN_DISK_FIBER_PEsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, peFiberThick / 2.); + TGeoVolume* middleBarrelConnDiskSIO2Volume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_FIBER_SIO2_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskSIO2, medSiO2); + TGeoVolume* middleBarrelConnDiskPEVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_FIBER_PE_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskPE, medPE); middleBarrelConnDiskSIO2Volume->SetLineColor(kGray); middleBarrelConnDiskPEVolume->SetLineColor(kGray); auto* rot = new TGeoRotation("", 0, 0, 180); - auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick / 2 + zLengthMiddleBarrel), rot); - auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick / 2 + zLengthMiddleBarrel), rot); + auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick / 2. + zLengthMiddleBarrel), rot); + auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick / 2. + zLengthMiddleBarrel), rot); motherVolume->AddNode(middleBarrelConnDiskSIO2Volume, 1, combiTransSIO2); motherVolume->AddNode(middleBarrelConnDiskPEVolume, 1, combiTransPE); - TGeoTube* middleBarrelConnDiskCu = new TGeoTube(Form("TRK_MIDBARCONN_DISK_CUsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, cuPowerThick); - TGeoTube* middleBarrelConnDiskPEPower = new TGeoTube(Form("TRK_MIDBARCONN_DISK_PEsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, pePowerThick); - TGeoVolume* middleBarrelConnDiskCuVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_CU_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskCu, medCu); - TGeoVolume* middleBarrelConnDiskPEPowerVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_PE_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskPEPower, medPE); + TGeoTube* middleBarrelConnDiskCu = new TGeoTube(Form("TRK_MIDBARCONN_DISK_POWER_CUsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, cuPowerThick / 2.); + TGeoTube* middleBarrelConnDiskPEPower = new TGeoTube(Form("TRK_MIDBARCONN_DISK_POWER_PEsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, pePowerThick / 2.); + TGeoVolume* middleBarrelConnDiskCuVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_POWER_CU_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskCu, medCu); + TGeoVolume* middleBarrelConnDiskPEPowerVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_POWER_PE_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskPEPower, medPE); middleBarrelConnDiskCuVolume->SetLineColor(kGray); middleBarrelConnDiskPEPowerVolume->SetLineColor(kGray); - auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick / 2 + zLengthMiddleBarrel), rot); - auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick / 2 + zLengthMiddleBarrel), rot); + auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick / 2. + zLengthMiddleBarrel), rot); + auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick / 2. + zLengthMiddleBarrel), rot); motherVolume->AddNode(middleBarrelConnDiskCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarrelConnDiskPEPowerVolume, 1, combiTransPEPower); - TGeoTube* middleBarrelConnDiskPU = new TGeoTube(Form("TRK_MIDBARCONN_DISK_PUsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, puCoolingThick); - TGeoTube* middleBarrelConnDiskH2O = new TGeoTube(Form("TRK_MIDBARCONN_DISK_H2Osh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, h2oCoolingThick); + TGeoTube* middleBarrelConnDiskPU = new TGeoTube(Form("TRK_MIDBARCONN_DISK_PUsh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, puCoolingThick / 2.); + TGeoTube* middleBarrelConnDiskH2O = new TGeoTube(Form("TRK_MIDBARCONN_DISK_H2Osh_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), rMinMiddleBarrelDisk, rMaxMiddleBarrelDisk, h2oCoolingThick / 2.); TGeoVolume* middleBarrelConnDiskPUVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_PU_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskPU, medPU); TGeoVolume* middleBarrelConnDiskH2OVolume = new TGeoVolume(Form("TRK_MIDBARCONN_DISK_H2O_%s", orientation == Orientation::kASide ? "bwd" : "fwd"), middleBarrelConnDiskH2O, medH2O); middleBarrelConnDiskPUVolume->SetLineColor(kGray); middleBarrelConnDiskH2OVolume->SetLineColor(kGray); - motherVolume->AddNode(middleBarrelConnDiskPUVolume, 1, combiTransCu); - motherVolume->AddNode(middleBarrelConnDiskH2OVolume, 1, combiTransPEPower); + auto* combiTransPU = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick / 2. + zLengthMiddleBarrel), rot); + auto* combiTransH2O = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick + h2oCoolingThick / 2. + zLengthMiddleBarrel), rot); + motherVolume->AddNode(middleBarrelConnDiskPUVolume, 1, combiTransPU); + motherVolume->AddNode(middleBarrelConnDiskH2OVolume, 1, combiTransH2O); } // Barrel to forward connection disks float rMaxMiddleServicesBarFwd = 74.5f + siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick + h2oCoolingThick; for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { // Create fibers: 3.07mm, 50% SiO2, 50% PE - TGeoTube* middleBarFwdFiberSIO2 = new TGeoTube("TRK_MIDBARFWD_FIBER_SIO2sh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, siO2FiberThick); - TGeoTube* middleBarFwdFiberPE = new TGeoTube("TRK_MIDBARFWD_FIBER_PEsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, peFiberThick); + TGeoTube* middleBarFwdFiberSIO2 = new TGeoTube("TRK_MIDBARFWD_FIBER_SIO2sh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, siO2FiberThick / 2.); + TGeoTube* middleBarFwdFiberPE = new TGeoTube("TRK_MIDBARFWD_FIBER_PEsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, peFiberThick / 2.); TGeoVolume* middleBarFwdFiberSIO2Volume = new TGeoVolume("TRK_MIDBARFWD_FIBER_SIO2", middleBarFwdFiberSIO2, medSiO2); TGeoVolume* middleBarFwdFiberPEVolume = new TGeoVolume("TRK_MIDBARFWD_FIBER_PE", middleBarFwdFiberPE, medPE); middleBarFwdFiberSIO2Volume->SetLineColor(kGray); middleBarFwdFiberPEVolume->SetLineColor(kGray); auto* rot = new TGeoRotation("", 0, 0, 180); - auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick / 2 + zLengthMiddleServices), rot); - auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick / 2 + zLengthMiddleServices), rot); + auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick / 2. + zLengthMiddleServices), rot); + auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick / 2. + zLengthMiddleServices), rot); motherVolume->AddNode(middleBarFwdFiberSIO2Volume, 1, combiTransSIO2); motherVolume->AddNode(middleBarFwdFiberPEVolume, 1, combiTransPE); // Create powerlines: 10.9mm, 9% Cu, 91% PE - TGeoTube* middleBarFwdPowerCu = new TGeoTube("TRK_MIDBARFWD_POWER_CUsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, cuPowerThick); - TGeoTube* middleBarFwdPowerPE = new TGeoTube("TRK_MIDBARFWD_POWER_PEsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, pePowerThick); + TGeoTube* middleBarFwdPowerCu = new TGeoTube("TRK_MIDBARFWD_POWER_CUsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, cuPowerThick / 2.); + TGeoTube* middleBarFwdPowerPE = new TGeoTube("TRK_MIDBARFWD_POWER_PEsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, pePowerThick / 2.); TGeoVolume* middleBarFwdPowerCuVolume = new TGeoVolume("TRK_MIDBARFWD_POWER_CU", middleBarFwdPowerCu, medCu); TGeoVolume* middleBarFwdPowerPEVolume = new TGeoVolume("TRK_MIDBARFWD_POWER_PE", middleBarFwdPowerPE, medPE); middleBarFwdPowerCuVolume->SetLineColor(kGray); middleBarFwdPowerPEVolume->SetLineColor(kGray); - auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick / 2 + zLengthMiddleServices), rot); - auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick / 2 + zLengthMiddleServices), rot); + auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick / 2. + zLengthMiddleServices), rot); + auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick / 2. + zLengthMiddleServices), rot); motherVolume->AddNode(middleBarFwdPowerCuVolume, 1, combiTransCu); motherVolume->AddNode(middleBarFwdPowerPEVolume, 1, combiTransPEPower); // Create cooling pipes: 4.74mm, 56% PU, 44% H2O - TGeoTube* middleBarFwdCoolingPU = new TGeoTube("TRK_MIDBARFWD_COOLING_PUsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, puCoolingThick); - TGeoTube* middleBarFwdCoolingH2O = new TGeoTube("TRK_MIDBARFWD_COOLING_H2Osh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, h2oCoolingThick); + TGeoTube* middleBarFwdCoolingPU = new TGeoTube("TRK_MIDBARFWD_COOLING_PUsh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, puCoolingThick / 2.); + TGeoTube* middleBarFwdCoolingH2O = new TGeoTube("TRK_MIDBARFWD_COOLING_H2Osh", rMinMiddleBarrel, rMaxMiddleServicesBarFwd, h2oCoolingThick / 2.); TGeoVolume* middleBarFwdCoolingPUVolume = new TGeoVolume("TRK_MIDBARFWD_COOLING_PU", middleBarFwdCoolingPU, medPU); TGeoVolume* middleBarFwdCoolingH2OVolume = new TGeoVolume("TRK_MIDBARFWD_COOLING_H2O", middleBarFwdCoolingH2O, medH2O); middleBarFwdCoolingPUVolume->SetLineColor(kGray); middleBarFwdCoolingH2OVolume->SetLineColor(kGray); - auto* combiTransCoolingPU = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick / 2 + zLengthMiddleServices), rot); - auto* combiTransCoolingH2O = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick + h2oCoolingThick / 2 + zLengthMiddleServices), rot); + auto* combiTransCoolingPU = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick / 2. + zLengthMiddleServices), rot); + auto* combiTransCoolingH2O = new TGeoCombiTrans(0, 0, (int)orientation * (siO2FiberThick + peFiberThick + cuPowerThick + pePowerThick + puCoolingThick + h2oCoolingThick / 2. + zLengthMiddleServices), rot); motherVolume->AddNode(middleBarFwdCoolingPUVolume, 1, combiTransCoolingPU); motherVolume->AddNode(middleBarFwdCoolingH2OVolume, 1, combiTransCoolingH2O); } @@ -446,6 +468,10 @@ void TRKServices::createMiddleServices(TGeoVolume* motherVolume) void TRKServices::createOuterBarrelServices(TGeoVolume* motherVolume) { + // This implements a service barrel around the full outer tracker which is probably not needed: + // power, data and cooling should be implemented on the staves + // Used only for 'LOI' geometry + auto& matmgr = o2::base::MaterialManager::Instance(); TGeoMedium* medSiO2 = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_SILICONDIOXIDE"); @@ -498,5 +524,540 @@ void TRKServices::createOuterBarrelServices(TGeoVolume* motherVolume) motherVolume->AddNode(outerBarrelCoolingPUVolume, 1, nullptr); 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 hardcodes the yellow shape for the middle services + auto& matmgr = o2::base::MaterialManager::Instance(); + + TGeoMedium* medSiO2 = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_SILICONDIOXIDE"); + TGeoMedium* medPE = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_POLYETHYLENE"); + TGeoMedium* medCu = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_COPPER"); + TGeoMedium* medPU = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_POLYURETHANE"); + TGeoMedium* medH2O = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_WATER"); + TGeoMedium* medCFiber = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_CARBONFIBERM55J6K"); + + // Barrel service constants + const int ITBarrelnFiber = 70; + const int ITBarrelnPower = 70; + float siO2FiberAreaB = ITBarrelnFiber * mFiberArea * mFiberComposition[0]; + float peFiberAreaB = ITBarrelnFiber * mFiberArea * mFiberComposition[1]; + + float puCoolingAreaB = 0; + float h2oCoolingAreaB = 0; + float cuPowerAreaB = ITBarrelnPower * mPowerBundleArea * mPowerBundleComposition[0]; + float pePowerAreaB = ITBarrelnPower * mPowerBundleArea * mPowerBundleComposition[1]; + + // Disk service constants + const int ITDisknFiber = 3 * 24; + const int ITDisknPower = 3 * 16; + float siO2FiberAreaD = ITDisknFiber * mFiberArea * mFiberComposition[0]; + float peFiberAreaD = ITDisknFiber * mFiberArea * mFiberComposition[1]; + + float puCoolingAreaD = 0; + float h2oCoolingAreaD = 0; + float cuPowerAreaD = ITDisknPower * mPowerBundleArea * mPowerBundleComposition[0]; + float pePowerAreaD = ITDisknPower * mPowerBundleArea * mPowerBundleComposition[1]; + + // Carbon Fiber Cylinder support for the middle tracker + // (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); + LOGP(info, "Creating carbon fiber support for Middle Tracker"); + motherVolume->AddNode(middleBarrelCarbonSupportVolume, 1, nullptr); + + // Get geometry information from TRK which is already present + 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 + LOGP(info, "Building services for barrel Middle Layers"); + + // Middle barrel connection disks + const float rMinMiddleBarrelDisk = 5.68f; + const float rMaxMiddleBarrelDisk = rMinMiddleServices; + auto orientation = Orientation::kASide; + float diskCircumference = rMaxMiddleBarrelDisk * 3.14; // Use only half circumference + + 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(kOrange - 9); + auto* rot = new TGeoRotation("", 0, 0, 180); // Why this? + auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); + + zCur += 2. * dZ; + 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(kOrange - 9); + auto* combiTransPE = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); + + motherVolume->AddNode(middleBarrelConnDiskSIO2Volume, 1, combiTransSIO2); + motherVolume->AddNode(middleBarrelConnDiskPEVolume, 1, combiTransPE); + + zCur += 2. * dZ; + 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(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(kOrange - 9); + auto* combiTransPEPower = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); + motherVolume->AddNode(middleBarrelConnDiskCuVolume, 1, combiTransCu); + motherVolume->AddNode(middleBarrelConnDiskPEPowerVolume, 1, combiTransPEPower); + + for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { + for (int iSide = 0; iSide < 2; iSide++) { // left/right or top/bottom + float refAngle = 0; + std::string orLabel("A"); + if (orientation == Orientation::kCSide) { + orLabel = "C"; + refAngle = 90; + } + // Add ML Disk services + // 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, zLengthCylinderMiddleServicesDisk / 2, -45, 45); + TGeoVolume* middleDiskFiberSIO2Volume = new TGeoVolume(Form("TRK_MLD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleDiskFiberSIO2, medSiO2); + 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, zLengthCylinderMiddleServicesDisk / 2, -45, 45); + TGeoVolume* middleDiskFiberPEVolume = new TGeoVolume(Form("TRK_MLD_FIBER_PE_%s%d", orLabel.c_str(), iSide), middleDiskFiberPE, medPE); + 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, zLengthCylinderMiddleServicesDisk / 2, -45, 45); + TGeoVolume* middleDiskPowerCuVolume = new TGeoVolume(Form("TRK_MLD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleDiskPowerCu, medCu); + 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, zLengthCylinderMiddleServicesDisk / 2, -45, 45); + TGeoVolume* middleDiskPowerPEVolume = new TGeoVolume(Form("TRK_MLD_POWER_PE_%s%d", orLabel.c_str(), iSide), middleDiskPowerPE, medPE); + middleDiskPowerPEVolume->SetLineColor(kOrange + 1); + + motherVolume->AddNode(middleDiskPowerCuVolume, 1, combiTrans); + motherVolume->AddNode(middleDiskPowerPEVolume, 1, combiTrans); + + if (orientation == Orientation::kASide) { + // Add Barrel services + // 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, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); + TGeoVolume* middleBarrelFiberSIO2Volume = new TGeoVolume(Form("TRK_MLB_FIBER_SIO2_A%d", iSide), middleBarrelFiberSIO2, medSiO2); + 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, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); + TGeoVolume* middleBarrelFiberPEVolume = new TGeoVolume(Form("TRK_MLB_FIBER_PE_A%d", iSide), middleBarrelFiberPE, medPE); + 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, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); + TGeoVolume* middleBarrelPowerCuVolume = new TGeoVolume(Form("TRK_MLB_POWER_CU_A%d", iSide), middleBarrelPowerCu, medCu); + 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, zLengthCylinderMiddleServicesBarrel / 2, -45, 45); + TGeoVolume* middleBarrelPowerPEVolume = new TGeoVolume(Form("TRK_MLB_POWER_PE_A%d", iSide), middleBarrelPowerPE, medPE); + middleBarrelPowerPEVolume->SetLineColor(kOrange - 9); + + motherVolume->AddNode(middleBarrelPowerCuVolume, 1, combiTrans); + motherVolume->AddNode(middleBarrelPowerPEVolume, 1, combiTrans); + + // TODO: add cooling ducts/pipes + } + } + } + + // Barrel to forward connection disks + // 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; + std::string orLabel("A"); + if (orientation == Orientation::kCSide) { + refAngle = 90; + orLabel = "C"; + } + double totalThickness = 0; + for (int iSide = 0; iSide < 2; iSide++) { + // Create fibers + double zCur = zMiddleServicesBarrelFwdConnection; // Change to f + double dZ = siO2FiberAreaD / diskCircumference / 2.; + totalThickness += 2 * dZ; + if (orientation == Orientation::kASide) { + dZ += siO2FiberAreaB / diskCircumference / 2.; + } + TGeoTubeSeg* middleBarFwdFiberSIO2 = new TGeoTubeSeg(Form("TRK_MIDBARFWD_FIBER_SIO2sh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); + TGeoVolume* middleBarFwdFiberSIO2Volume = new TGeoVolume(Form("TRK_MIDBARFWD_FIBER_SIO2_%s%d", orLabel.c_str(), iSide), middleBarFwdFiberSIO2, medSiO2); + auto* rot = new TGeoRotation("", refAngle + iSide * 180., 0, 180.); + auto* combiTransSIO2 = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); + + zCur += 2 * dZ; + dZ = peFiberAreaD / diskCircumference / 2.; + totalThickness += 2 * dZ; + if (orientation == Orientation::kASide) { + dZ += peFiberAreaB / diskCircumference / 2.; + } + 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(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); + + // Create powerlines + zCur += 2 * dZ; + dZ = cuPowerAreaD / diskCircumference / 2.; + totalThickness += 2 * dZ; + if (orientation == Orientation::kASide) { + dZ += cuPowerAreaB / diskCircumference / 2.; + } + TGeoTubeSeg* middleBarFwdPowerCu = new TGeoTubeSeg(Form("TRK_MIDBARFWD_POWER_CUsh_%s%d", orLabel.c_str(), iSide), rMinMiddleBarrel, rMaxMiddleServicesBarFwd, dZ, -45, 45); + TGeoVolume* middleBarFwdPowerCuVolume = new TGeoVolume(Form("TRK_MIDBARFWD_POWER_CU_%s%d", orLabel.c_str(), iSide), middleBarFwdPowerCu, medCu); + auto* combiTransCu = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), rot); + + zCur += 2 * dZ; + dZ = pePowerAreaD / diskCircumference / 2.; + totalThickness += 2 * dZ; + if (orientation == Orientation::kASide) { + dZ += pePowerAreaB / diskCircumference / 2.; + } + 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(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); + + // TODO: add cooling ducts/pipes + } + + // Forward part + float zLengthMiddleServicesFwd = 350.f - (zMiddleServicesBarrelFwdConnection + totalThickness); + float rMinMiddleServicesFwd = 74.5f; // 74.5cm + + for (int iSide = 0; iSide < 2; iSide++) { + // Create fibers + + float translation = (int)orientation * (zMiddleServicesBarrelFwdConnection + totalThickness + zLengthMiddleServicesFwd / 2); + + double rCur = rMinMiddleServicesFwd; + double dR = siO2FiberAreaD / (3.14 * rCur); + if (orientation == Orientation::kASide) { + dR += siO2FiberAreaB / (3.14 * rCur); + } + 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(kOrange + 1); + + rCur += dR; + dR = peFiberAreaD / (3.14 * rCur); + if (orientation == Orientation::kASide) { + dR += peFiberAreaB / (3.14 * rCur); + } + 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(kOrange + 1); + + auto* rot = new TGeoRotation("", refAngle + iSide * 180., 0, 0.); + auto* combiTrans = new TGeoCombiTrans(0, 0, translation, rot); + motherVolume->AddNode(middleFwdFiberSIO2Volume, 1, combiTrans); + motherVolume->AddNode(middleFwdFiberPEVolume, 1, combiTrans); + + // Create powerlines + rCur += dR; + dR = cuPowerAreaD / (3.14 * rCur); + if (orientation == Orientation::kASide) { + dR += cuPowerAreaB / (3.14 * rCur); + } + 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(kOrange + 1); + + rCur += dR; + dR = pePowerAreaD / (3.14 * rCur); + if (orientation == Orientation::kASide) { + dR += pePowerAreaB / (3.14 * rCur); + } + 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(kOrange + 1); + motherVolume->AddNode(middleFwdPowerCuVolume, 1, combiTrans); + motherVolume->AddNode(middleFwdPowerPEVolume, 1, combiTrans); + + // TODO: add cooling ducts/pipes + } + } +} + +void TRKServices::createOTServicesPeacock(TGeoVolume* motherVolume) +{ + // This implments the service barrels for power + data for the OT barrels and disks + // TODO: add cooling + + auto& matmgr = o2::base::MaterialManager::Instance(); + + TGeoMedium* medSiO2 = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_SILICONDIOXIDE"); + TGeoMedium* medPE = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_POLYETHYLENE"); + TGeoMedium* medCu = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_COPPER"); + TGeoMedium* medPU = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_POLYURETHANE"); + TGeoMedium* medH2O = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_WATER"); + TGeoMedium* medCFiber = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_CARBONFIBERM55J6K"); + + // OT Disk service constants + const int OTDisknFiber = 3 * 51; + const int OTDisknPower = 3 * 34; + float siO2FiberAreaD = OTDisknFiber * mFiberArea * mFiberComposition[0]; + float peFiberAreaD = OTDisknFiber * mFiberArea * mFiberComposition[1]; + + float puCoolingAreaD = 0; + float h2oCoolingAreaD = 0; + float cuPowerAreaD = OTDisknPower * mPowerBundleArea * mPowerBundleComposition[0]; + float pePowerAreaD = OTDisknPower * mPowerBundleArea * mPowerBundleComposition[1]; + + // OT Barrel service constants + const int OTBarrelnFiber = 460; + const int OTBarrelnPower = 306; + float siO2FiberAreaB = OTBarrelnFiber * mFiberArea * mFiberComposition[0]; + float peFiberAreaB = OTBarrelnFiber * mFiberArea * mFiberComposition[1]; + + float puCoolingAreaB = 0; + float h2oCoolingAreaB = 0; + float cuPowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[0]; + float pePowerAreaB = OTBarrelnPower * mPowerBundleArea * mPowerBundleComposition[1]; + + // 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 + float rMaxOuterCarbonSupport = 82.4f; // 4 mm of carbon fiber + const float zLengthOuterCarbon = 280.0f; // Rough guess for now + TGeoTube* outerBarrelCarbonSupport = new TGeoTube("TRK_OT_CARBONSUPPORTsh", rMinOuterCarbonSupport, rMaxOuterCarbonSupport, zLengthOuterCarbon / 2.); + TGeoVolume* outerBarrelCarbonSupportVolume = new TGeoVolume("TRK_OT_CARBONSUPPORT", outerBarrelCarbonSupport, medCFiber); + outerBarrelCarbonSupportVolume->SetLineColor(kGray); + LOGP(info, "Creating carbon fiber support for Outer Tracker"); + motherVolume->AddNode(outerBarrelCarbonSupportVolume, 1, nullptr); + + for (auto& orientation : {Orientation::kASide, Orientation::kCSide}) { + std::string orLabel = "A"; + float refAngle = 0; + if (orientation == Orientation::kCSide) { + orLabel = "C"; + refAngle = 90; + } + // TODO: add cables/connections at ends of OT barrels + double zCur = zOTbarrelServices; + + 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(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 * 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(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 * 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); + 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 * 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(kAzure + 6); + combiTrans = new TGeoCombiTrans(0, 0, (int)orientation * (zCur + dZ), nullptr); + motherVolume->AddNode(outerBarrelPowerPEVolume, 1, combiTrans); + + for (int iSide = 0; iSide < 2; iSide++) { + // #### 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(kMagenta); + + rCur += dR; + 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(kMagenta); + + 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 for disks + rCur += dR; + 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(kMagenta + 1); + + rCur += dR; + 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(kMagenta + 1); + motherVolume->AddNode(outerDisksPowerCuVolume, 1, combiTrans); + motherVolume->AddNode(outerDisksPowerPEVolume, 1, combiTrans); + + // TODO: add cooling ducts/pipes + } + } +} + } // namespace trk -} // namespace o2 \ No newline at end of file +} // namespace o2 diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h index d80027593cef0..282fc72becc52 100644 --- a/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/TRKSimulationLinkDef.h @@ -15,14 +15,19 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::trk::TRKPetalCase + ; -#pragma link C++ class o2::trk::TRKLayer + ; -#pragma link C++ class o2::trk::TRKPetalLayer + ; -#pragma link C++ class o2::trk::TRKPetalDisk + ; +#pragma link C++ class o2::trk::Hit + ; +#pragma link C++ class std::vector < o2::trk::Hit> + ; + +#pragma link C++ class o2::trk::TRKCylindricalLayer + ; +#pragma link C++ class o2::trk::TRKSegmentedLayer + ; +#pragma link C++ class o2::trk::TRKMLLayer + ; +#pragma link C++ class o2::trk::TRKOTLayer + ; +#pragma link C++ class o2::trk::VDLayer + ; #pragma link C++ class o2::trk::TRKServices + ; #pragma link C++ class o2::trk::Detector + ; #pragma link C++ class o2::base::DetImpl < o2::trk::Detector> + ; #pragma link C++ class o2::trk::Digitizer + ; +#pragma link C++ class o2::trk::ChipSimResponse + ; #pragma link C++ class o2::trk::DPLDigitizerParam < o2::detectors::DetID::TRK> + ; #pragma link C++ class o2::trk::DPLDigitizerParam < o2::detectors::DetID::FT3> + ; diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx new file mode 100644 index 0000000000000..809923b048234 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDGeometryBuilder.cxx @@ -0,0 +1,1136 @@ +// 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 "TRKSimulation/VDGeometryBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "TGeoManager.h" +#include "Framework/Logger.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKSimulation/VDLayer.h" +#include "TRKSimulation/VDSensorRegistry.h" +#include +#include + +namespace o2::trk +{ + +static std::vector gVDSensors; // stays in this TU only +std::vector& vdSensorRegistry() { return gVDSensors; } + +void clearVDSensorRegistry() { gVDSensors.clear(); } + +void registerSensor(const std::string& volName, int petal, VDSensorDesc::Region region, VDSensorDesc::Type type, int idx) +{ + gVDSensors.push_back({volName, petal, region, type, idx}); +} + +static inline std::string makeSensorName(const std::string& layerName, int layerNumber) +{ + return Form("%s_%s%d", layerName.c_str(), o2::trk::GeometryTGeo::getTRKSensorPattern(), layerNumber); +} + +namespace +{ + +// Config: which volumes count as SOLIDS to subtract from the vacuum volume +inline bool isSolidToCut(const TGeoVolume* v) +{ + const char* nm = v->GetName(); + const char* med = v->GetMedium() ? v->GetMedium()->GetName() : ""; + // silicon sensors (barrel + disks) + if (med && strcmp(med, "TRK_SILICON$") == 0) { + return true; + } + // walls, sidewalls, cold-plate, service rings (names from your builders) + if (TString(nm).BeginsWith("VD_InnerWallArc")) { + return true; + } + if (TString(nm).BeginsWith("VD_OuterWallArc")) { + return true; + } + if (TString(nm).BeginsWith("VD_SideWall")) { + return true; + } + if (TString(nm).BeginsWith("VD_InnerWallCyl")) { + return true; + } + if (TString(nm).BeginsWith("VD_OuterWallCyl")) { + return true; + } + if (TString(nm).Contains("_Coldplate")) { + return true; + } + if (TString(nm).BeginsWith("IRIS_Service_Neg")) { + return true; + } + if (TString(nm).BeginsWith("IRIS_Service_Pos_InVac")) { + return true; + } + if (TString(nm).BeginsWith("VD_InclinedWall")) { + return true; + } + if (TString(nm).Contains("_ZCap")) { + return true; + } + return false; +} + +// Ensure every leaf shape has a stable, informative name +inline const char* ensureShapeName(TGeoVolume* v) +{ + auto* sh = v->GetShape(); + TString nm = sh->GetName(); + if (nm.IsNull() || nm.BeginsWith("TGeo")) { + TString wanted = TString(v->GetName()) + "_sh"; + // avoid collisions + int k = 0; + TString cand = wanted; + auto* shapes = gGeoManager ? gGeoManager->GetListOfShapes() : nullptr; + while (shapes && shapes->FindObject(cand)) { + cand = Form("%s_%d", wanted.Data(), ++k); + } + sh->SetName(cand); + if (shapes && !shapes->FindObject(cand)) { + shapes->Add(sh); + } + } + return sh->GetName(); +} + +// Recorder state for the petal-local composite +static TString gPetalSolidsFormula; +static int gLocalTrIdx = 0; + +// add "ShapeName:IRIS_LOC_TR_k" to the petal-local formula (no outer rotation) +inline void appendLocalTerm(const char* shapeName, const TGeoHMatrix& H) +{ + auto* ct = new TGeoCombiTrans(H); + ct->SetName(Form("IRIS_LOC_TR_%d", gLocalTrIdx++)); + ct->RegisterYourself(); + if (!gPetalSolidsFormula.IsNull()) { + gPetalSolidsFormula += "+"; + } + gPetalSolidsFormula += TString::Format("%s:%s", shapeName, ct->GetName()); +} + +// DFS: compose LOCAL transforms only (identity prefix), to capture the petal contents +void traversePetalLocal(TGeoVolume* vol, const TGeoHMatrix& prefix) +{ + auto* nodes = vol->GetNodes(); + if (!nodes) { + return; + } + for (int i = 0; i < nodes->GetEntriesFast(); ++i) { + auto* node = (TGeoNode*)nodes->At(i); + auto* childV = node->GetVolume(); + TGeoHMatrix H(prefix); + if (auto* m = node->GetMatrix()) { + H.Multiply(m); + } + + if (isSolidToCut(childV)) { + const char* shapeName = ensureShapeName(childV); + appendLocalTerm(shapeName, H); + } + traversePetalLocal(childV, H); + } +} + +// Build (once) a petal-local composite containing ONLY solids (walls, silicon, coldplate, services, disks) +inline void buildPetalSolidsComposite(TGeoVolume* petalAsm) +{ + // If it already exists, skip + if (gGeoManager && gGeoManager->GetListOfShapes() && gGeoManager->GetListOfShapes()->FindObject("IRIS_PETAL_SOLIDSsh")) { + return; + } + + gPetalSolidsFormula.Clear(); + gLocalTrIdx = 0; + + TGeoHMatrix I; // identity + traversePetalLocal(petalAsm, I); + + if (gPetalSolidsFormula.IsNull()) { + LOGP(error, "IRIS_PETAL_SOLIDSsh formula is empty; did not find solids in petal."); + return; + } + + LOGP(info, "IRIS_PETAL_SOLIDSsh formula: {}", gPetalSolidsFormula.Data()); + new TGeoCompositeShape("IRIS_PETAL_SOLIDSsh", gPetalSolidsFormula.Data()); +} + +// Build the global cutout by rotating the petal-local composite n times with (p+0.5) phase +inline void buildIrisCutoutFromPetalSolid(int nPetals) +{ + auto* shps = gGeoManager->GetListOfShapes(); + auto* base = shps ? dynamic_cast(shps->FindObject("IRIS_PETAL_SOLIDSsh")) : nullptr; + if (!base) { + LOGP(error, "IRIS cutout: shape 'IRIS_PETAL_SOLIDSsh' not found."); + return; + } + + // IMPORTANT: for nPetals==1, a composite expression like "A:tr" is invalid. + // Just clone the petal solids shape as the global cutout. + if (nPetals == 1) { + // Remove any previous shape with same name if it exists (optional but keeps things clean) + if (shps->FindObject("IRIS_CUTOUTsh")) { + // ROOT shape lists are owned by gGeoManager; removing is not always necessary. + // Keeping it simple: just create a unique name if it already exists. + LOGP(warning, "IRIS cutout: 'IRIS_CUTOUTsh' already exists; overwriting by clone name reuse may be unsafe."); + } + + auto* cut = dynamic_cast(base->Clone("IRIS_CUTOUTsh")); + if (!cut) { + LOGP(error, "IRIS cutout: failed to clone 'IRIS_PETAL_SOLIDSsh' to 'IRIS_CUTOUTsh'."); + return; + } + + LOGP(info, "IRIS_CUTOUTsh created as clone of IRIS_PETAL_SOLIDSsh (nPetals=1)."); + return; + } + + // nPetals > 1: build union of rotated copies + TString cutFormula; + for (int p = 0; p < nPetals; ++p) { + const double phi = (360.0 / nPetals) * (p + 0.5); + auto* R = new TGeoRotation(); + R->RotateZ(phi); + auto* RT = new TGeoCombiTrans(0, 0, 0, R); + RT->SetName(Form("IRIS_PETAL_ROT_%d", p)); + RT->RegisterYourself(); + + if (p) { + cutFormula += "+"; + } + cutFormula += Form("IRIS_PETAL_SOLIDSsh:%s", RT->GetName()); + } + + LOGP(info, "IRIS_CUTOUTsh formula: {}", cutFormula.Data()); + auto* cut = new TGeoCompositeShape("IRIS_CUTOUTsh", cutFormula.Data()); + (void)cut; + + // Stronger sanity: ensure it parsed into a boolean node + auto* cutCheck = dynamic_cast(shps->FindObject("IRIS_CUTOUTsh")); + if (!cutCheck || !cutCheck->GetBoolNode()) { + LOGP(error, "IRIS cutout sanity: IRIS_CUTOUTsh exists but parsing failed (no BoolNode)."); + } else { + LOGP(info, "IRIS cutout sanity: OK ({} petals).", nPetals); + } +} + +} // namespace + +// =================== Specs & constants (ROOT units: cm) =================== +static constexpr double kX2X0 = 0.001f; // 0.1% X0 per layer +static constexpr double kLenZ_cm = 50.0f; // L0/L1/L2 Z length + +// Radii (cm) +static constexpr double rL0_cm = 0.5f; // 5 mm +static constexpr double rL1_cm = 1.2f; // 12 mm +static constexpr double rL2_cm = 2.5f; // 25 mm + +// IRIS5 rectangular L0 width (cm) +static constexpr double kL0RectHeight_cm = 0.5f; // 5.0 mm +static constexpr double kL0RectWidth_cm = 0.83f; // 8.3 mm + +// Disks radii (cm) +static constexpr double diskRin_cm = 0.5f; // 5 mm +static constexpr double diskRout_cm = 2.5f; // 25 mm +static const double diskZ_cm[6] = {-34.0f, -30.0f, -26.0f, 26.0f, 30.0f, 34.0f}; + +// Petal walls specifications (cm) +static constexpr double kPetalZ_cm = 70.0f; // full wall height +static constexpr double kWallThick_cm = 0.02f; // 0.2 mm +static constexpr double kInnerWallRadius_cm = 0.48f; // 4.8 mm (ALWAYS cylindrical) +static constexpr double kOuterWallRadius_cm = 4.8f; // 48 mm (can be changed) +static constexpr double kEps_cm = 2.5e-4f; +static constexpr double kEps_100um = 0.01f; // 100 microns in cm + +// 3 inclined walls ("walls") specs for the full-cylinder option +// Thickness in-plane (cm). This is the short half-dimension of the TGeoBBox in XY. +static constexpr double kInclinedWallThick_cm = 0.04f; // 0.4 mm +// Layer-shell thickness used for the gap boundaries in the inclined-wall construction (cm) +static constexpr double kSiLayerThick_cm = 0.01f; // 0.1 mm +// Base tangency angle (deg) for the first wall; the other 2 are +120/+240. +// This matches the angle used in the ROOT sketch from our chat. +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 (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) ========== +namespace +{ + +// Convert a linear gap at radius R into an angular gap (deg) +inline double degFromArc(double arc, double radius) +{ + // arc and radius in the SAME units (cm or mm); result in degrees + return (radius > 0.f) ? (arc / radius) * TMath::RadToDeg() : 0.f; +} + +/** + * Compute silicon segment φ-span (degrees) inside one petal, + * when you know the number of petals and the linear gap at a given radius. + * + * All of: gap and radius must be in the SAME units (cm or mm). + * If you use cm everywhere (ROOT default), pass gap_cm and radius_cm. + */ +inline double phiSpanFromGap(int nPetals, double gap, double radius) +{ + if (nPetals <= 0 || radius <= 0.f) { + return 0.f; + } + const double petalPhiDeg = 360.f / nPetals; + const double phi = petalPhiDeg - degFromArc(gap, radius); + return phi > 0.f ? phi : 0.f; +} + +/** + * Compute silicon segment φ-span (degrees) from a known arc length at a given radius. + * arcLen and radius must be in the SAME units (cm or mm). + */ +inline double phiSpanFromArc(double arcLen, double radius) +{ + return (arcLen > 0.f && radius > 0.f) ? degFromArc(arcLen, radius) : 0.f; +} + +inline TGeoCombiTrans rotZ(double phiDeg) +{ + auto* r = new TGeoRotation(); + r->RotateZ(static_cast(phiDeg)); + return TGeoCombiTrans(0., 0., 0., r); +} +} // namespace + +// ============ Petal sub-builders (LOCAL coords only, no rotation) ========= + +// Walls: inner cylindrical arc at r=4.8 mm (always), outer arc wall, and two side plates. +static void addPetalWalls(TGeoVolume* petalAsm, + int nPetals, + double outerRadius_cm = kOuterWallRadius_cm, + bool withSideWalls = true, + bool fullCylindricalRadialWalls = false) +{ + if (!petalAsm) { + LOGP(error, "addPetalWalls: petalAsm is null"); + return; + } + + auto& matmgr = o2::base::MaterialManager::Instance(); + const TGeoMedium* med = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083"); + + if (!med) { + LOGP(warning, "Petal walls: ALICE3_TRKSERVICES_ALUMINIUM5083$ not found, walls not created."); + return; + } + + const double halfZ = 0.5 * kPetalZ_cm; + + // In full-cylinder radial-wall mode we ignore nPetals for the radial walls. + const double halfPhi = fullCylindricalRadialWalls ? 180.0 : 0.5 * (360.0 / static_cast(nPetals)); + + // ---- Inner radial wall ---- + if (fullCylindricalRadialWalls) { + auto* s = new TGeoTube(static_cast(kInnerWallRadius_cm), + static_cast(kInnerWallRadius_cm + kWallThick_cm), + static_cast(halfZ)); + auto* v = new TGeoVolume("VD_InnerWallCyl", s, med); + v->SetLineColor(kGray + 2); + v->SetTransparency(70); + petalAsm->AddNode(v, 1); + } else { + auto* s = new TGeoTubeSeg(static_cast(kInnerWallRadius_cm), + static_cast(kInnerWallRadius_cm + kWallThick_cm), + static_cast(halfZ), + static_cast(-halfPhi), + static_cast(+halfPhi)); + auto* v = new TGeoVolume("VD_InnerWallArc", s, med); + v->SetLineColor(kGray + 2); + v->SetTransparency(70); + petalAsm->AddNode(v, 1); + } + + // ---- Outer radial wall ---- + if (fullCylindricalRadialWalls) { + auto* s = new TGeoTube(static_cast(outerRadius_cm), + static_cast(outerRadius_cm + kWallThick_cm), + static_cast(halfZ)); + auto* v = new TGeoVolume("VD_OuterWallCyl", s, med); + v->SetLineColor(kGray + 2); + v->SetTransparency(70); + petalAsm->AddNode(v, 1); + } else { + auto* s = new TGeoTubeSeg(static_cast(outerRadius_cm), + static_cast(outerRadius_cm + kWallThick_cm), + static_cast(halfZ), + static_cast(-halfPhi), + static_cast(+halfPhi)); + auto* v = new TGeoVolume("VD_OuterWallArc", s, med); + v->SetLineColor(kGray + 2); + v->SetTransparency(70); + petalAsm->AddNode(v, 1); + } + + // ---- Side plates (skip in "single petal full cylinders" mode) ---- + if (!withSideWalls) { + return; + } + + // ---- Side walls (boxes) at ±halfPhi ---- + const double radialLen = (outerRadius_cm - (kInnerWallRadius_cm + kWallThick_cm)); + auto* sideS = new TGeoBBox(static_cast(0.5f * radialLen), + static_cast(0.5f * kWallThick_cm), + static_cast(halfZ)); + auto* sideV = new TGeoVolume("VD_SideWall", sideS, med); + sideV->SetLineColor(kGray + 2); + sideV->SetTransparency(70); + + for (int sgn : {-1, +1}) { + const double phi = sgn * halfPhi; + const double rMid = kInnerWallRadius_cm + kWallThick_cm + 0.5f * radialLen; + const double rad = static_cast(TMath::DegToRad()); + const double x = rMid * std::cos(phi * rad); + const double y = rMid * std::sin(phi * rad); + auto* rot = new TGeoRotation(); + rot->RotateZ(static_cast(phi)); + auto* tr = new TGeoCombiTrans(static_cast(x), + static_cast(y), + 0.0, rot); + petalAsm->AddNode(sideV, (sgn < 0 ? 1 : 2), tr); + } +} + +// Build inner layers (L0..L2). L0 may be rectangular (IRIS5) or cylindrical. +// φ-spans derive from spec gaps/arc; all local placement (no rotation). +static void addBarrelLayers(TGeoVolume* petalAsm, int nPetals, int petalID, bool rectangularL0, bool fullCylinders) +{ + if (!petalAsm) { + LOGP(error, "addBarrelLayers: petalAsm is null"); + return; + } + + // Per spec (mm → cm) + constexpr double gapL0_cm = 0.163f; // 1.63 mm + constexpr double gapL1L2_cm = 0.12f; // 1.2 mm + constexpr double arcL0_cm = 0.6247f; // 6.247 mm + + // φ spans + const double phiL0_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL0_cm, rL0_cm); + const double phiL1_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL1_cm); + const double phiL2_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm); + + const std::string nameL0 = + std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" + + std::string(o2::trk::GeometryTGeo::getTRKPetalLayerPattern()) + "0"; + + if (!fullCylinders && rectangularL0) { + VDRectangularLayer L0(0, + nameL0, + kX2X0, kL0RectWidth_cm, kLenZ_cm, kLenZ_cm); + + // Correct translation: move to radius + half width along x + double x = kL0RectHeight_cm + L0.getChipThickness() / 2.; + LOGP(info, "Placing rectangular L0 at r={:.3f} cm (half-width={:.3f} cm)", x, 0.5f * kL0RectWidth_cm); + double y = 0.0; + double z = 0.0; + + // Correct rotation: rotate 90 degrees around z so long side is horizontal + auto* rot = new TGeoRotation(); + rot->RotateZ(90.0); + + auto* tr = new TGeoCombiTrans(x, y, z, rot); + L0.createLayer(petalAsm, tr); + registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Plane, /*idx*/ 0); + } else { + VDCylindricalLayer L0(0, + nameL0, + kX2X0, rL0_cm, phiL0_deg, kLenZ_cm, kLenZ_cm); + L0.createLayer(petalAsm, nullptr); + registerSensor(makeSensorName(nameL0, 0), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 0); + } + + const std::string nameL1 = + std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" + + std::string(o2::trk::GeometryTGeo::getTRKPetalLayerPattern()) + "1"; + + VDCylindricalLayer L1(1, + nameL1, + kX2X0, rL1_cm, phiL1_deg, kLenZ_cm, kLenZ_cm); + L1.createLayer(petalAsm, nullptr); + registerSensor(makeSensorName(nameL1, 1), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 1); + + const std::string nameL2 = + std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" + + std::string(o2::trk::GeometryTGeo::getTRKPetalLayerPattern()) + "2"; + + VDCylindricalLayer L2(2, + nameL2, + kX2X0, rL2_cm, phiL2_deg, kLenZ_cm, kLenZ_cm); + L2.createLayer(petalAsm, nullptr); + registerSensor(makeSensorName(nameL2, 2), petalID, VDSensorDesc::Region::Barrel, VDSensorDesc::Type::Curved, /*idx*/ 2); +} + +// Build cold plate (cylindrical) in local coordinates, and add it to the petal assembly. +static void addColdPlate(TGeoVolume* petalAsm, int nPetals, int petalId, bool fullCylinders = false) +{ + if (!petalAsm) { + LOGP(error, "addColdPlate: petalAsm is null"); + return; + } + + // Resolve medium: prefer provided medium, otherwise try to fetch from geo manager + const TGeoMedium* med = gGeoManager->GetMedium("ALICE3_TRKSERVICES_CERAMIC"); + if (!med) { + LOGP(error, "addColdPlate: can't find the medium."); + } + + // Angular span for one petal (deg) + constexpr double gapL1L2_cm = 0.12f; // 1.2 mm + + // φ spans + const double phiSpanColdplate_deg = + fullCylinders ? 360.0 : phiSpanFromGap(nPetals, gapL1L2_cm, rL2_cm); // L2 gap-defined in normal mode + const double halfPhiDeg = 0.5 * phiSpanColdplate_deg; + const double startPhi = -halfPhiDeg; + const double endPhi = +halfPhiDeg; + + // Build tube segment: inner radius, outer radius = inner + thickness, half-length Z + auto* shape = new TGeoTubeSeg(static_cast(kColdplateRadius_cm), + static_cast(kColdplateRadius_cm + kColdplateThickness_cm), + static_cast(0.5 * kColdplateZ_cm), + static_cast(startPhi), + static_cast(endPhi)); + + TString volName = TString::Format("Petal%d_Coldplate", petalId); + auto* coldVol = new TGeoVolume(volName, shape, med); + coldVol->SetLineColor(kAzure - 3); + coldVol->SetTransparency(10); + + // Place in local petal coordinates (no extra transform); keep object alive by allocating shape/volume on heap. + petalAsm->AddNode(coldVol, 1); + + LOGP(info, "Adding cold plate {} r={:.3f} cm t={:.3f} cm Lz={:.3f} cm φ=[{:.3f}, {:.3f}]", + volName.Data(), kColdplateRadius_cm, kColdplateThickness_cm, kColdplateZ_cm, startPhi, endPhi); +} + +// Add IRIS service module(s) as aluminum annular cylinders placed outside the petals. +// The two modules are placed at z = ±(36 + halfLength). +static void addIRISServiceModules(TGeoVolume* petalAsm, int nPetals) +{ + if (!petalAsm) { + LOGP(error, "addIRISServiceModules: petalAsm is null"); + return; + } + + auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70); + const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl); + + if (!med) { + LOGP(error, "addIRISServiceModules: ALUMINUM medium not found."); + return; + } + + constexpr double radius = 3.2; // cm (inner radius) + constexpr double thickness = 0.133; // cm (radial thickness) + constexpr double halfLength = 19.5; // cm (half-length along Z) + const double rIn = radius; + const double rOut = radius + thickness; + + // Petal angular span. If you have an exact half-φ from your walls, use it here. + const double halfPhi_deg = 0.5 * (360.0 / double(nPetals)); + + // Create shape once and reuse + auto* segSh = new TGeoTubeSeg( + "IRIS_SERVICE_SEGsh", + rIn, rOut, + halfLength, + -halfPhi_deg, halfPhi_deg); + + // Positive Z module + TString namePos = "IRIS_Service_Pos"; + auto* volPos = new TGeoVolume(namePos, segSh, med); + volPos->SetLineColor(kRed + 2); + volPos->SetTransparency(50); + + // Negative Z module: reuse same shape object, give different name + TString nameNeg = "IRIS_Service_Neg"; + auto* volNeg = new TGeoVolume(nameNeg, segSh, med); + volNeg->SetLineColor(kRed + 2); + volNeg->SetTransparency(50); + + // Translations (heap-allocated so ROOT keeps them) + const double zpos = 36.0 + halfLength; + auto* transPos = new TGeoTranslation(0.0, 0.0, static_cast(zpos)); + auto* transNeg = new TGeoTranslation(0.0, 0.0, static_cast(-zpos)); + + // Add to mother volume + petalAsm->AddNode(volPos, 1, transPos); + petalAsm->AddNode(volNeg, 2, transNeg); + + LOGP(info, "Added IRIS service modules at z = ±{} cm, r=[{}, {}] cm", zpos, rIn, rOut); +} + +// Only the A-side "inside vacuum" piece participates in the cutout. +static void addIRISServiceModulesSegmented(TGeoVolume* petalAsm, int nPetals) +{ + if (!petalAsm) { + LOGP(error, "addIRISServiceModulesSegmented: petalAsm is null"); + return; + } + + // --- Service geometry (same as your previous values) + constexpr double rIn = 3.2; // cm + constexpr double thickness = 0.133; // cm + constexpr double rOut = rIn + thickness; + constexpr double halfLen = 19.5; // cm + constexpr double z0 = 36.0 + halfLen; // 55.5 cm center of +Z service + const double zMinA = z0 - halfLen; // 36.0 cm + const double zMaxA = z0 + halfLen; // 75.0 cm + + // --- Vacuum vessel window around z∈[-L/2, +L/2] with wall thickness on +Z side + // Keep these in sync with TRKServices::createVacuumCompositeShape() + constexpr double vacuumVesselLength = 76.0; // cm + constexpr double vacuumVesselThickness = 0.08; // cm (0.8 mm) + const double halfVess = 0.5 * vacuumVesselLength; // 38.0 cm + const double gapStart = halfVess; // 38.00 + const double gapEnd = halfVess + vacuumVesselThickness; // 38.08 + + // --- Petal φ-span (segment) + const double halfPhi = 0.5 * (360.0 / double(nPetals)); + + auto* matAl = new TGeoMaterial("ALUMINUM", 26.9815, 13, 2.70); + const TGeoMedium* med = new TGeoMedium("ALUMINUM", 4, matAl); + + if (!med) { + LOGP(error, "addIRISServiceModules: ALUMINUM medium not found."); + return; + } + + // ========================= + // C-side (negative Z) whole + // ========================= + { + auto* sh = new TGeoTubeSeg(rIn, rOut, halfLen, -halfPhi, +halfPhi); + auto* vN = new TGeoVolume("IRIS_Service_Neg", sh, med); + vN->SetLineColor(kRed + 2); + vN->SetTransparency(55); + petalAsm->AddNode(vN, 1, new TGeoTranslation(0., 0., -(z0))); + } + + // ===================================== + // A-side (positive Z): split with a gap + // ===================================== + // Piece 1 (INSIDE vacuum): z ∈ [zMinA, min(zMaxA, gapStart)] → goes into cutout + const double L_inVac = std::max(0.0, std::min(zMaxA, gapStart) - zMinA); // expected ~2.0 cm + if (L_inVac > 0) { + const double dz = 0.5 * L_inVac; + const double zc = zMinA + dz; // center of lower slice, ≈ 37.0 cm + auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, halfPhi); + sh->SetName("IRIS_SERVICE_POS_INVACsh"); + auto* vP = new TGeoVolume("IRIS_Service_Pos_InVac", sh, med); + vP->SetLineColor(kRed + 2); + vP->SetTransparency(55); + petalAsm->AddNode(vP, 1, new TGeoTranslation(0., 0., zc)); + LOGP(info, "IRIS A-side (InVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm", + zc - dz, zc + dz, 2 * dz); + } else { + LOGP(warning, "IRIS A-side (InVac): no overlap with vacuum (L_inVac<=0)"); + } + + // Gap (no material): (gapStart, gapEnd) = (38.00, 38.08) + + // Piece 2 (OUT of vacuum): z ∈ [max(zMinA, gapEnd), zMaxA] → NOT in cutout + const double L_outVac = std::max(0.0, zMaxA - std::max(zMinA, gapEnd)); // expected ~36.92 cm + if (L_outVac > 0) { + const double dz = 0.5 * L_outVac; + const double zc = std::max(zMinA, gapEnd) + dz; // center of upper slice + auto* sh = new TGeoTubeSeg(rIn, rOut, dz, -halfPhi, +halfPhi); + sh->SetName("IRIS_SERVICE_POS_OUTVACsh"); + auto* vP = new TGeoVolume("IRIS_Service_Pos_OutVac", sh, med); + vP->SetLineColor(kRed + 1); + vP->SetTransparency(70); + petalAsm->AddNode(vP, 2, new TGeoTranslation(0., 0., +zc)); + LOGP(info, "IRIS A-side (OutVac): z=[{:.3f},{:.3f}] cm, len={:.3f} cm", + zc - dz, zc + dz, 2 * dz); + } else { + LOGP(warning, "IRIS A-side (OutVac): no upper piece (L_outVac<=0)"); + } +} + +// Build disks in local coords: each disk gets only a local Z translation. +// φ span from gap at rOut. +static void addDisks(TGeoVolume* petalAsm, int nPetals, int petalID, bool fullCylinders) +{ + + if (!petalAsm) { + LOGP(error, "addDisks: petalAsm is null"); + return; + } + + const double phiDisk_deg = fullCylinders ? 360.0 : phiSpanFromGap(nPetals, 2 * kWallThick_cm, diskRin_cm); + + for (int i = 0; i < 6; ++i) { + const std::string nameD = + std::string(o2::trk::GeometryTGeo::getTRKPetalPattern()) + std::to_string(petalID) + "_" + + std::string(o2::trk::GeometryTGeo::getTRKPetalDiskPattern()) + std::to_string(i); + + VDDiskLayer disk(i, + nameD, + kX2X0, diskRin_cm, diskRout_cm, phiDisk_deg, diskZ_cm[i]); + + // Local Z placement only + auto* tr = new TGeoTranslation(0.0, 0.0, static_cast(disk.getZPosition())); + disk.createLayer(petalAsm, tr); + registerSensor(makeSensorName(nameD, i), petalID, VDSensorDesc::Region::Disk, VDSensorDesc::Type::Plane, /*idx*/ i); + } +} + +// Add Z end-cap walls to "close" the petal/cylinder volume at zMin and zMax. +// Implemented as thin rings (TGeoTube) with thickness 'capThick_cm' in Z, +// spanning radii [rIn_cm, rOut_cm]. +static void addPetalEndCaps(TGeoVolume* petalAsm, + int petalId, + double rIn_cm, + double rOut_cm, + double zMin_cm, + double zMax_cm, + double capThick_cm) +{ + if (!petalAsm) { + LOGP(error, "addPetalEndCaps: petalAsm is null"); + return; + } + + auto& matmgr = o2::base::MaterialManager::Instance(); + const TGeoMedium* med = + matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083"); + + if (!med) { + LOGP(warning, + "addPetalEndCaps: ALICE3_TRKSERVICES_ALUMINIUM5083 not found, caps not created."); + return; + } + + const double halfT = 0.5 * capThick_cm; + + auto* sh = new TGeoTube(static_cast(rIn_cm), + static_cast(rOut_cm), + static_cast(halfT)); + + TString vname = Form("Petal%d_ZCap", petalId); + auto* v = new TGeoVolume(vname, sh, med); + v->SetLineColor(kGray + 2); + v->SetTransparency(70); + + auto* trMin = new TGeoTranslation(0.0, 0.0, + static_cast(zMin_cm + halfT)); + auto* trMax = new TGeoTranslation(0.0, 0.0, + static_cast(zMax_cm - halfT)); + + petalAsm->AddNode(v, 1, trMin); + petalAsm->AddNode(v, 2, trMax); +} + +// Build one complete petal assembly (walls + L0..L2 + disks) in LOCAL coords. +static TGeoVolume* buildPetalAssembly(int nPetals, + int petalID, + bool rectangularL0, + bool fullCylinders, + bool withSideWalls) +{ + auto* petalAsm = new TGeoVolumeAssembly(Form("PETAL_%d", petalID)); + + // In the special mode: no side walls, but keep radial walls as FULL cylinders. + addPetalWalls(petalAsm, nPetals, kOuterWallRadius_cm, + /*withSideWalls=*/withSideWalls, + /*fullCylindricalRadialWalls=*/fullCylinders); + + addBarrelLayers(petalAsm, nPetals, petalID, rectangularL0, fullCylinders); + // addDisks(petalAsm, nPetals, petalID, fullCylinders); // disks removed according to the v3b layout + + addColdPlate(petalAsm, nPetals, petalID, /*fullCylinders=*/false); + addIRISServiceModulesSegmented(petalAsm, nPetals); + + return petalAsm; +} + +static TGeoVolume* buildFullCylAssembly(int petalID, bool withDisks) +{ + // IMPORTANT: keep naming consistent with createIRIS4/5 (PETAL_%d) + auto* petalAsm = new TGeoVolumeAssembly(Form("PETAL_%d", petalID)); + + // Radial walls only: full 360° cylinders, no side plates + addPetalWalls(petalAsm, + /*nPetals=*/1, + /*outerRadius_cm=*/kOuterWallRadius_cm, + /*withSideWalls=*/false, + /*fullCylindricalRadialWalls=*/true); + + // --- Z end-cap walls to close the petal in Z --- + { + const double zMin = -0.5 * kPetalZ_cm - 2 * kWallThick_cm; + const double zMax = +0.5 * kPetalZ_cm + 2 * kWallThick_cm; + const double rIn = kInnerWallRadius_cm; + const double rOut = kOuterWallRadius_cm + kWallThick_cm; + + addPetalEndCaps(petalAsm, + petalID, + rIn, + rOut, + zMin, + zMax, + kWallThick_cm); + } + + // Full 360° barrel cylinders + addBarrelLayers(petalAsm, + /*nPetals=*/1, + /*petalID=*/petalID, + /*rectangularL0=*/false, + /*fullCylinders=*/true); + + addColdPlate(petalAsm, 1, petalID, /*fullCylinders=*/true); + addIRISServiceModulesSegmented(petalAsm, /*nPetals=*/1); + + // Optionally add full 360° disks + if (withDisks) { + addDisks(petalAsm, + /*nPetals=*/1, + /*petalID=*/petalID, + /*fullCylinders=*/true); + } + + return petalAsm; +} + +// Add 3 inclined walls (straight walls) into a full-cylinder petal assembly. +// The walls are implemented as TWO TGeoBBox segments per wall, living in the gaps: +// - segment 01: from tangency at Rtan to inner surface of L1 +// - segment 12: from outer surface of L1 to inner surface of L2 +// The construction accounts for the finite wall thickness (kInclinedWallThick_cm). +static void addInclinedWalls3FullCyl(TGeoVolume* petalAsm, double phi0_deg = kInclinedWallPhi0_deg) +{ + if (!petalAsm) { + LOGP(error, "addInclinedWalls3FullCyl: petalAsm is null"); + return; + } + + auto& matmgr = o2::base::MaterialManager::Instance(); + const TGeoMedium* med = matmgr.getTGeoMedium("ALICE3_TRKSERVICES_ALUMINIUM5083"); + if (!med) { + LOGP(warning, "addInclinedWalls3FullCyl: ALICE3_TRKSERVICES_ALUMINIUM5083 not found, walls not created."); + return; + } + + // Clearance margin from layer/coldplate surfaces (cm) + constexpr double clearanceMargin = 0.010; // 100 microns + + // Geometry inputs (cm) + constexpr double R0 = rL0_cm; + constexpr double R1 = rL1_cm; + constexpr double R2 = rL2_cm; + constexpr double Rmax = kInclinedWallRmax_cm; + + const double wallDy = 0.5 * kInclinedWallThick_cm; + const double shellTh = kSiLayerThick_cm; // 0.1 mm shell thickness for bounds + const double h = 0.5 * shellTh; + const double dz = 0.5 * kPetalZ_cm; // match barrel/coldplate length in full-cyl option + + constexpr int nWalls = 3; + constexpr double dPhi = 360.0 / double(nWalls); + + // Gap boundaries (shell surfaces) + const double R0_out = R0 + h; + const double R1_in = R1 - h; + const double R1_out = R1 + h; + const double R2_in = R2 - h; + const double R2_out = R2 + h; + + // Coldplate outer radius (tube segment is [kColdplateRadius_cm, kColdplateRadius_cm + kColdplateThickness_cm]) + const double Rcold_out = kColdplateRadius_cm + kColdplateThickness_cm; + + // Tangency radius choice (thickness-safe at s=0): need Rtan - wallDy >= R0_out + const double Rtan = R0_out + wallDy + clearanceMargin; + + // For finite-thickness box: + // outermost edge uses Reff_plus, innermost edge uses Reff_minus + const double Reff_plus = Rtan + wallDy + clearanceMargin; + const double Reff_minus = std::max(0.0, Rtan - wallDy - clearanceMargin); + + auto sAt = [](double R, double Reff) -> double { + const double v = R * R - Reff * Reff; + return (v > 0.0) ? std::sqrt(v) : 0.0; + }; + + // Segment bounds in 's' (thickness-safe): + // 01: from tangency to L1 inner surface (outer edge <= R1_in) + const double sa01 = 0.0; + const double sb01 = sAt(R1_in, Reff_plus); + + // 12: from outside L1 to inside L2 + const double sa12 = sAt(R1_out, Reff_minus); // inner edge >= R1_out + const double sb12 = sAt(R2_in, Reff_plus); // outer edge <= R2_in + + // 23: from outside coldplate (and outside L2) to Rmax + const double R23_start = std::max(R2_out, Rcold_out) + clearanceMargin; + const double sa23 = sAt(R23_start, Reff_minus); // inner edge >= start radius + const double sb23 = sAt(Rmax, Reff_plus); // outer edge <= Rmax + + if (!((sb01 > sa01) && (sb12 > sa12) && (sb23 > sa23))) { + LOGP(error, + "addInclinedWalls3FullCyl: invalid bounds. 01:[{},{}] 12:[{},{}] 23:[{},{}] " + "Rtan={} Reff-={} Reff+={} R23_start={}", + sa01, sb01, sa12, sb12, sa23, sb23, + Rtan, Reff_minus, Reff_plus, R23_start); + return; + } + + // Half-lengths and center parameters (s-centers) + const double dx01 = 0.5 * (sb01 - sa01); + const double dx12 = 0.5 * (sb12 - sa12); + const double dx23 = 0.5 * (sb23 - sa23); + + const double sc01 = 0.5 * (sa01 + sb01); + const double sc12 = 0.5 * (sa12 + sb12); + const double sc23 = 0.5 * (sa23 + sb23); + + // Create shapes once, reuse for all walls + auto* sh01 = new TGeoBBox(dx01, wallDy, dz); + auto* sh12 = new TGeoBBox(dx12, wallDy, dz); + auto* sh23 = new TGeoBBox(dx23, wallDy, dz); + sh01->SetName("VD_InclinedWall01_sh"); + sh12->SetName("VD_InclinedWall12_sh"); + sh23->SetName("VD_InclinedWall23_sh"); + + const double phi0_rad = phi0_deg * TMath::DegToRad(); + + for (int i = 0; i < nWalls; ++i) { + const double phi = phi0_rad + i * (dPhi * TMath::DegToRad()); + const double cosPhi = std::cos(phi); + const double sinPhi = std::sin(phi); + + // Tangency point on Rtan + const double xT = Rtan * cosPhi; + const double yT = Rtan * sinPhi; + + // Tangent direction u = (-sin, cos) + const double ux = -sinPhi; + const double uy = cosPhi; + + // Centers (in XY) + const double cx01 = xT + sc01 * ux; + const double cy01 = yT + sc01 * uy; + const double cx12 = xT + sc12 * ux; + const double cy12 = yT + sc12 * uy; + const double cx23 = xT + sc23 * ux; + const double cy23 = yT + sc23 * uy; + + // Rotation: local X along tangent => angle = phi + 90° + const double alpha_deg = phi0_deg + i * dPhi + 90.0; + auto* rot = new TGeoRotation(); + rot->RotateZ(alpha_deg); + + // Create volumes per wall (unique names) + auto* v01 = new TGeoVolume(Form("VD_InclinedWall01_%d", i), sh01, med); + auto* v12 = new TGeoVolume(Form("VD_InclinedWall12_%d", i), sh12, med); + auto* v23 = new TGeoVolume(Form("VD_InclinedWall23_%d", i), sh23, med); + v01->SetLineColor(kOrange + 7); + v12->SetLineColor(kOrange + 7); + v23->SetLineColor(kOrange + 7); + v01->SetTransparency(70); + v12->SetTransparency(70); + v23->SetTransparency(70); + + auto* T01 = new TGeoCombiTrans(cx01, cy01, 0.0, rot); + auto* T12 = new TGeoCombiTrans(cx12, cy12, 0.0, new TGeoRotation(*rot)); + auto* T23 = new TGeoCombiTrans(cx23, cy23, 0.0, new TGeoRotation(*rot)); + + petalAsm->AddNode(v01, 1, T01); + petalAsm->AddNode(v12, 1, T12); + petalAsm->AddNode(v23, 1, T23); + + LOGP(debug, + "InclinedWall {}: 01({:.3f},{:.3f}) 12({:.3f},{:.3f}) 23({:.3f},{:.3f}) angle={:.2f}°", + i, cx01, cy01, cx12, cy12, cx23, cy23, alpha_deg); + } +} + +// =================== Public entry points =================== + +void createIRIS4Geometry(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRIS4Geometry: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 4; + for (int p = 0; p < nPetals; ++p) { + auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false, + /*fullCylinders=*/false, + /*withSideWalls=*/true); + // Build the petal-local solids composite once from the FIRST petal + if (p == 0) { + buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords + } + const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5); + auto* R = new TGeoRotation(); + R->RotateZ(phiDeg); + auto* T = new TGeoCombiTrans(0, 0, 0, R); + motherVolume->AddNode(petal, p + 1, T); + } + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createIRIS5Geometry(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRIS5Geometry: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 4; + for (int p = 0; p < nPetals; ++p) { + auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ true, + /*fullCylinders=*/false, + /*withSideWalls=*/true); + // Build the petal-local solids composite once from the FIRST petal + if (p == 0) { + buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords + } + const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5); + auto* R = new TGeoRotation(); + R->RotateZ(phiDeg); + auto* T = new TGeoCombiTrans(0, 0, 0, R); + motherVolume->AddNode(petal, p + 1, T); + } + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createIRIS4aGeometry(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRIS4aGeometry: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 3; + for (int p = 0; p < nPetals; ++p) { + auto* petal = buildPetalAssembly(nPetals, p, /*rectangularL0*/ false, + /*fullCylinders=*/false, + /*withSideWalls=*/true); + // Build the petal-local solids composite once from the FIRST petal + if (p == 0) { + buildPetalSolidsComposite(petal); // <-- captures only SOLIDS in local coords + } + const double phiDeg = (360.0 / double(nPetals)) * (double(p) + 0.5); + auto* R = new TGeoRotation(); + R->RotateZ(phiDeg); + auto* T = new TGeoCombiTrans(0, 0, 0, R); + motherVolume->AddNode(petal, p + 1, T); + } + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createIRISGeometryFullCyl(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRISGeometryFullCyl: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 1; + constexpr int petalID = 0; + + auto* petal = buildFullCylAssembly(petalID, /*withDisks=*/false); + motherVolume->AddNode(petal, 1, nullptr); + + buildPetalSolidsComposite(petal); + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createIRISGeometry3InclinedWalls(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRISGeometry3InclinedWalls: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 1; + constexpr int petalID = 0; + + // Start from the same content as createIRISGeometryFullCyl + auto* petal = buildFullCylAssembly(petalID, /*withDisks=*/false); + + // Add the 3 inclined walls into the same assembly + addInclinedWalls3FullCyl(petal, kInclinedWallPhi0_deg); + + motherVolume->AddNode(petal, 1, nullptr); + + // Same cutout pipeline as full-cyl + buildPetalSolidsComposite(petal); + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createIRISGeometryFullCylwithDisks(TGeoVolume* motherVolume) +{ + if (!motherVolume) { + LOGP(error, "createIRISGeometryFullCylDisks: motherVolume is null"); + return; + } + + clearVDSensorRegistry(); + + constexpr int nPetals = 1; + constexpr int petalID = 0; + + auto* petal = buildFullCylAssembly(petalID, /*withDisks=*/true); + motherVolume->AddNode(petal, 1, nullptr); + + // Same cutout pipeline as createIRIS4/5: + buildPetalSolidsComposite(petal); + buildIrisCutoutFromPetalSolid(nPetals); +} + +void createSinglePetalDebug(TGeoVolume* motherVolume, int petalID, int nPetals, bool rectangularL0) +{ + auto* petal = buildPetalAssembly(nPetals, petalID, rectangularL0, false, true); + + // Optionally rotate the petal for display + const double phiDeg = (360.f / static_cast(nPetals)) * (static_cast(petalID) + 0.5f); + auto* R = new TGeoCombiTrans(0, 0, 0, new TGeoRotation("", phiDeg, 0, 0)); + motherVolume->AddNode(petal, 1, R); + + LOGP(info, "Debug: Added Petal{} to {}", petalID, motherVolume->GetName()); +} + +} // namespace o2::trk diff --git a/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx new file mode 100644 index 0000000000000..a92dcd24d6038 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/simulation/src/VDLayer.cxx @@ -0,0 +1,601 @@ +// 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 "TRKSimulation/VDLayer.h" +#include "TRKBase/GeometryTGeo.h" +#include "TRKBase/Specs.h" + +#include "Framework/Logger.h" + +#include "TGeoTube.h" +#include "TGeoBBox.h" +#include "TGeoVolume.h" +#include "TGeoMatrix.h" +#include "TGeoManager.h" + +#include "TMath.h" + +namespace o2 +{ +namespace trk +{ + +// Helper function for floating point comparison +inline bool isFullCircle(double phiSpanDeg, double epsilon = 0.005) +{ + return (std::fabs(phiSpanDeg - 360.0) < epsilon); +} + +// Base layer constructor +VDLayer::VDLayer(int layerNumber, const std::string& layerName, double layerX2X0) + : mLayerNumber(layerNumber), mLayerName(layerName), mX2X0(layerX2X0), mModuleWidth(4.54) +{ + constexpr double kSiX0_cm = 9.5; // Radiation length of Silicon in cm + mChipThickness = mX2X0 * kSiX0_cm; + + mSensorThickness = o2::trk::constants::VD::silicon::thickness; // cm +} + +// VDCylindricalLayer constructor +VDCylindricalLayer::VDCylindricalLayer(int layerNumber, const std::string& layerName, double layerX2X0, double radius, + double phiSpanDeg, double lengthZ, double lengthSensZ) + : VDLayer(layerNumber, layerName, layerX2X0), mRadius(radius), mPhiSpanDeg(phiSpanDeg), mLengthZ(lengthZ), mLengthSensZ(lengthSensZ) +{ + LOGP(info, "Creating VD cylindrical layer: id: {} name: {} x2X0: {} radius: {} phiSpanDeg: {} lengthZ: {} lengthSensZ: {} chipThickness = {} cm", + mLayerNumber, layerName, mX2X0, radius, phiSpanDeg, lengthZ, lengthSensZ, mChipThickness); +} + +// VDRectangularLayer constructor +VDRectangularLayer::VDRectangularLayer(int layerNumber, const std::string& layerName, double layerX2X0, + double width, double lengthZ, double lengthSensZ) + : VDLayer(layerNumber, layerName, layerX2X0), mWidth(width), mLengthZ(lengthZ), mLengthSensZ(lengthSensZ) +{ + + if (mLengthSensZ <= 0 || mLengthSensZ > mLengthZ) { + LOGP(fatal, "Invalid sensor length: sensZ={} layerZ={}", mLengthSensZ, mLengthZ); + } + LOGP(info, "Creating VD rectangular layer: id: {} name: {} x2X0: {} width: {} lengthZ: {} lengthSensZ: {} chipThickness = {} cm", + mLayerNumber, layerName, mX2X0, width, lengthZ, lengthSensZ, mChipThickness); +} + +// VDDiskLayer constructor +VDDiskLayer::VDDiskLayer(int layerNumber, const std::string& layerName, double layerX2X0, double rMin, double rMax, + double phiSpanDeg, double zPos) + : VDLayer(layerNumber, layerName, layerX2X0), mRMin(rMin), mRMax(rMax), mPhiSpanDeg(phiSpanDeg), mZPos(zPos) +{ + + LOGP(info, "Creating VD disk layer: id: {} name: {} x2X0: {} rMin: {} rMax: {} phiSpanDeg: {} zPos: {} chipThickness = {} cm", + mLayerNumber, layerName, mX2X0, rMin, rMax, phiSpanDeg, zPos, mChipThickness); +} + +/* +** Create sensor +*/ + +TGeoVolume* VDCylindricalLayer::createSensor() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + std::string sensName = Form("%s_%s%d", this->mLayerName.c_str(), GeometryTGeo::getTRKSensorPattern(), this->mLayerNumber); + const double rIn = mRadius; + const double rOut = mRadius + mSensorThickness; + const double halfZ = 0.5 * mLengthSensZ; + TGeoShape* shape; + if (isFullCircle(mPhiSpanDeg)) { + shape = new TGeoTube(rIn, rOut, halfZ); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; // degrees + shape = new TGeoTubeSeg(rIn, rOut, halfZ, -halfPhi, +halfPhi); + } + auto* vol = new TGeoVolume(sensName.c_str(), shape, medSi); + vol->SetLineColor(kYellow); + vol->SetTransparency(30); + return vol; +} + +TGeoVolume* VDRectangularLayer::createSensor() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + std::string sensName = Form("%s_%s%d", this->mLayerName.c_str(), GeometryTGeo::getTRKSensorPattern(), this->mLayerNumber); + const double hx = 0.5 * mWidth; + const double hy = 0.5 * mSensorThickness; + const double hz = 0.5 * mLengthSensZ; // <-- use sensor Z length, not full layer + + auto* shape = new TGeoBBox(hx, hy, hz); + auto* vol = new TGeoVolume(sensName.c_str(), shape, medSi); + vol->SetLineColor(kYellow); + vol->SetTransparency(30); + + return vol; +} + +TGeoVolume* VDDiskLayer::createSensor() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + if (mRMin < 0 || mRMax <= mRMin || mChipThickness <= 0 || mPhiSpanDeg <= 0 || mPhiSpanDeg > 360.0) { + LOGP(error, "Invalid disk sensor dims: rMin={}, rMax={}, t={}, phiSpanDeg={}", + mRMin, mRMax, mChipThickness, mPhiSpanDeg); + return nullptr; + } + std::string sensName = Form("%s_%s%d", this->mLayerName.c_str(), GeometryTGeo::getTRKSensorPattern(), this->mLayerNumber); + const double halfThickness = 0.5 * mSensorThickness; // active sensor thickness along Z + + // Same geometry as the layer (identical radii + phi span + thickness) + TGeoShape* shape; + if (isFullCircle(mPhiSpanDeg)) { + shape = new TGeoTube(mRMin, mRMax, halfThickness); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; // degrees + shape = new TGeoTubeSeg(mRMin, mRMax, halfThickness, -halfPhi, +halfPhi); + } + + auto* sensVol = new TGeoVolume(sensName.c_str(), shape, medSi); + sensVol->SetLineColor(kYellow); + sensVol->SetTransparency(30); + + return sensVol; +} + +/* +** Create metal stack +*/ + +TGeoVolume* VDCylindricalLayer::createMetalStack() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + const double metalT = mChipThickness - mSensorThickness; + if (metalT <= 0) { + return nullptr; // nothing to add + } + + std::string name = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKMetalStackPattern(), mLayerNumber); + + const double rIn = mRadius + mSensorThickness; + const double rOut = mRadius + mChipThickness; + const double halfZ = 0.5 * mLengthSensZ; + + TGeoShape* shape; + if (isFullCircle(mPhiSpanDeg)) { + shape = new TGeoTube(rIn, rOut, halfZ); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; + shape = new TGeoTubeSeg(rIn, rOut, halfZ, -halfPhi, +halfPhi); + } + auto* vol = new TGeoVolume(name.c_str(), shape, medSi); + vol->SetLineColor(kGray); + vol->SetTransparency(30); + return vol; +} + +TGeoVolume* VDRectangularLayer::createMetalStack() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + const double metalT = mChipThickness - mSensorThickness; + if (metalT <= 0) { + return nullptr; + } + + std::string name = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKMetalStackPattern(), mLayerNumber); + + const double hx = 0.5 * mWidth; + const double hy = 0.5 * metalT; + const double hz = 0.5 * mLengthSensZ; + + auto* shape = new TGeoBBox(hx, hy, hz); + auto* vol = new TGeoVolume(name.c_str(), shape, medSi); + vol->SetLineColor(kGray); + vol->SetTransparency(30); + return vol; +} + +TGeoVolume* VDDiskLayer::createMetalStack() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + const double metalT = mChipThickness - mSensorThickness; + if (metalT <= 0) { + return nullptr; + } + + if (mRMin < 0 || mRMax <= mRMin || mPhiSpanDeg <= 0 || mPhiSpanDeg > 360.0) { + LOGP(error, "Invalid disk metal dims: rMin={}, rMax={}, metalT={}, phiSpanDeg={}", + mRMin, mRMax, metalT, mPhiSpanDeg); + return nullptr; + } + + std::string name = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKMetalStackPattern(), mLayerNumber); + + const double halfThickness = 0.5 * metalT; + + TGeoShape* shape; + if (isFullCircle(mPhiSpanDeg)) { + shape = new TGeoTube(mRMin, mRMax, halfThickness); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; + shape = new TGeoTubeSeg(mRMin, mRMax, halfThickness, -halfPhi, +halfPhi); + } + auto* vol = new TGeoVolume(name.c_str(), shape, medSi); + vol->SetLineColor(kGray); + vol->SetTransparency(30); + return vol; +} + +/* +** Create chip +*/ + +TGeoVolume* VDCylindricalLayer::createChip() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + std::string chipName = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKChipPattern(), mLayerNumber); + + const double rIn = mRadius; + const double rOut = mRadius + mChipThickness; + const double halfZ = 0.5 * mLengthSensZ; + + TGeoShape* chipShape; + if (isFullCircle(mPhiSpanDeg)) { + chipShape = new TGeoTube(rIn, rOut, halfZ); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; + chipShape = new TGeoTubeSeg(rIn, rOut, halfZ, -halfPhi, +halfPhi); + } + auto* chipVol = new TGeoVolume(chipName.c_str(), chipShape, medSi); + + // sensor + if (auto* sensVol = createSensor()) { + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + chipVol->AddNode(sensVol, 1, nullptr); + } + + // metal stack + if (auto* metalVol = createMetalStack()) { + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), chipVol->GetName()); + chipVol->AddNode(metalVol, 1, nullptr); // concentric, no translation needed + } + + chipVol->SetLineColor(kYellow); + chipVol->SetTransparency(30); + return chipVol; +} + +TGeoVolume* VDRectangularLayer::createChip() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + auto* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + std::string chipName = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKChipPattern(), mLayerNumber); + + const double hx = 0.5 * mWidth; + const double hy = 0.5 * mChipThickness; + const double hz = 0.5 * mLengthSensZ; + + auto* chipShape = new TGeoBBox(hx, hy, hz); + auto* chipVol = new TGeoVolume(chipName.c_str(), chipShape, medSi); + + // sensor (place it on the "bottom" side, like TRK) + if (auto* sensVol = createSensor()) { + auto* transSens = new TGeoTranslation(0.0, -(mChipThickness - mSensorThickness) / 2, 0.0); + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + chipVol->AddNode(sensVol, 1, transSens); + } + + // metal stack (remaining thickness on top) + if (auto* metalVol = createMetalStack()) { + auto* transMetal = new TGeoTranslation(0.0, +mSensorThickness / 2, 0.0); + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), chipVol->GetName()); + chipVol->AddNode(metalVol, 1, transMetal); + } + + chipVol->SetLineColor(kYellow); + chipVol->SetTransparency(30); + return chipVol; +} + +TGeoVolume* VDDiskLayer::createChip() const +{ + if (!gGeoManager) { + LOGP(error, "gGeoManager is null"); + return nullptr; + } + TGeoMedium* medSi = gGeoManager->GetMedium("TRK_SILICON$"); + if (!medSi) { + LOGP(error, "Missing medium TRK_SILICON$"); + return nullptr; + } + + if (mRMin < 0 || mRMax <= mRMin || mChipThickness <= 0 || + mPhiSpanDeg <= 0 || mPhiSpanDeg > 360.0) { + LOGP(error, "Invalid disk chip dims: rMin={}, rMax={}, t={}, phi={}", + mRMin, mRMax, mChipThickness, mPhiSpanDeg); + return nullptr; + } + + std::string chipName = Form("%s_%s%d", mLayerName.c_str(), + GeometryTGeo::getTRKChipPattern(), mLayerNumber); + + const double halfThickness = 0.5 * mChipThickness; + + TGeoShape* chipShape; + if (isFullCircle(mPhiSpanDeg)) { + chipShape = new TGeoTube(mRMin, mRMax, halfThickness); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; + chipShape = new TGeoTubeSeg(mRMin, mRMax, halfThickness, -halfPhi, +halfPhi); + } + auto* chipVol = new TGeoVolume(chipName.c_str(), chipShape, medSi); + chipVol->SetLineColor(kYellow); + chipVol->SetTransparency(30); + + // Sensor slab (sensitive) placed on one side in Z (TRK-like stacking convention) + if (auto* sensVol = createSensor()) { + const double zSens = -(mChipThickness - mSensorThickness) / 2.0; + auto* tSens = new TGeoTranslation(0.0, 0.0, zSens); + LOGP(debug, "Inserting {} in {} ", sensVol->GetName(), chipVol->GetName()); + chipVol->AddNode(sensVol, 1, tSens); + } + + // Metal stack slab (non-sensitive), remaining thickness, also silicon + if (auto* metalVol = createMetalStack()) { + const double zMetal = +mSensorThickness / 2.0; + auto* tMetal = new TGeoTranslation(0.0, 0.0, zMetal); + LOGP(debug, "Inserting {} in {} ", metalVol->GetName(), chipVol->GetName()); + chipVol->AddNode(metalVol, 1, tMetal); + } + + return chipVol; +} + +/* +** Create layer +*/ + +// Cylindrical layer +void VDCylindricalLayer::createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans) const +{ + if (!motherVolume || !gGeoManager) { + LOGP(error, "Null motherVolume or gGeoManager"); + return; + } + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + if (!medAir) { + LOGP(error, "Missing TRK_AIR$"); + return; + } + + // Sanity + if (mRadius <= 0 || mChipThickness <= 0 || mLengthZ <= 0 || + mPhiSpanDeg <= 0 || mPhiSpanDeg > 360.0 || + mLengthSensZ <= 0 || mLengthSensZ > mLengthZ) { + LOGP(error, "Invalid cylindrical dimensions: r={}, t={}, Z={}, phi={}, sensZ={}", + mRadius, mChipThickness, mLengthZ, mPhiSpanDeg, mLengthSensZ); + return; + } + + // AIR container (layer) + const double rIn = mRadius; + const double rOut = mRadius + mChipThickness; + const double halfZ = 0.5 * mLengthZ; + + TGeoShape* layerShape; + if (isFullCircle(mPhiSpanDeg)) { + layerShape = new TGeoTube(rIn, rOut, halfZ); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; // degrees + layerShape = new TGeoTubeSeg(rIn, rOut, halfZ, -halfPhi, +halfPhi); + } + auto* layerVol = new TGeoVolume(mLayerName.c_str(), layerShape, medAir); + layerVol->SetLineColor(kYellow); + layerVol->SetTransparency(30); + + // Chip volume (must use mLengthSensZ internally) + TGeoVolume* chipVol = VDCylindricalLayer::createChip(); + if (!chipVol) { + LOGP(error, "VDCylindricalLayer::createChip() returned null"); + return; + } + LOGP(debug, "Inserting {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(chipVol, 1, nullptr); + + // Tiling: edge-to-edge if sensor shorter than layer; else single centered + // const auto zCenters = (mLengthSensZ < mLengthZ) + // ? centersNoGapZ(mLengthZ, mLengthSensZ) + // : std::vector{0.0}; + // + // int copyNo = 1; + // for (double zc : zCenters) { + // TGeoTranslation tz(0.0, 0.0, zc); + // layerVol->AddNode(sensorVol, copyNo++, (zc == 0.0 && zCenters.size() == 1) ? nullptr : &tz); + // } + + motherVolume->AddNode(layerVol, 1, combiTrans); +} + +// Rectangular layer +void VDRectangularLayer::createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans) const +{ + if (!motherVolume || !gGeoManager) { + LOGP(error, "Null motherVolume or gGeoManager"); + return; + } + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + if (!medAir) { + LOGP(error, "Missing TRK_AIR$"); + return; + } + + if (mWidth <= 0 || mChipThickness <= 0 || mLengthZ <= 0 || + mLengthSensZ <= 0 || mLengthSensZ > mLengthZ) { + LOGP(error, "Invalid rectangular dims: W={}, t={}, Z={}, sensZ={}", + mWidth, mChipThickness, mLengthZ, mLengthSensZ); + return; + } + + // AIR container (layer) + const double hx = 0.5 * mWidth; + const double hy = 0.5 * mChipThickness; + const double hz = 0.5 * mLengthZ; + + auto* layerShape = new TGeoBBox(hx, hy, hz); + auto* layerVol = new TGeoVolume(mLayerName.c_str(), layerShape, medAir); + layerVol->SetLineColor(kYellow); + layerVol->SetTransparency(30); + + // Sensor volume (uses mLengthSensZ internally) + TGeoVolume* chipVol = VDRectangularLayer::createChip(); + if (!chipVol) { + LOGP(error, "VDRectangularLayer::chipVol() returned null"); + return; + } + + LOGP(debug, "Inserting {} in {} ", chipVol->GetName(), layerVol->GetName()); + layerVol->AddNode(chipVol, 1, nullptr); + + // Tiling along Z, edge - to - edge if needed + // const auto zCenters = (mLengthSensZ < mLengthZ) + // ? centersNoGapZ(mLengthZ, mLengthSensZ) + // : std::vector{0.0}; + // + // int copyNo = 1; + // for (double zc : zCenters) { + // TGeoTranslation tz(0.0, 0.0, zc); + // layerVol->AddNode(sensorVol, copyNo++, (zc == 0.0 && zCenters.size() == 1) ? nullptr : &tz); + // } + + motherVolume->AddNode(layerVol, 1, combiTrans); +} + +// Disk layer +void VDDiskLayer::createLayer(TGeoVolume* motherVolume, TGeoMatrix* combiTrans) const +{ + if (!motherVolume || !gGeoManager) { + LOGP(error, "Null motherVolume or gGeoManager"); + return; + } + TGeoMedium* medAir = gGeoManager->GetMedium("TRK_AIR$"); + if (!medAir) { + LOGP(error, "Missing TRK_AIR$"); + return; + } + + if (mRMin < 0 || mRMax <= mRMin || mChipThickness <= 0 || + mPhiSpanDeg <= 0 || mPhiSpanDeg > 360.0) { + LOGP(error, "Invalid disk dims: rMin={}, rMax={}, t={}, phi={}", + mRMin, mRMax, mChipThickness, mPhiSpanDeg); + return; + } + + // For disks the thickness is along Z and equals mChipThickness + const double halfThickness = 0.5 * mChipThickness; + + // AIR container (layer) + TGeoShape* layerShape; + if (isFullCircle(mPhiSpanDeg)) { + layerShape = new TGeoTube(mRMin, mRMax, halfThickness); + } else { + const double halfPhi = 0.5 * mPhiSpanDeg; + layerShape = new TGeoTubeSeg(mRMin, mRMax, halfThickness, -halfPhi, +halfPhi); + } + auto* layerVol = new TGeoVolume(mLayerName.c_str(), layerShape, medAir); + layerVol->SetLineColor(kYellow); + layerVol->SetTransparency(30); + + // Sensor (same size & shape as the layer for disks) + TGeoVolume* chipVol = VDDiskLayer::createChip(); + if (!chipVol) { + LOGP(error, "VDDiskLayer::createChip() returned null"); + return; + } + + // Insert single sensor (no Z-segmentation for disks) + layerVol->AddNode(chipVol, 1, nullptr); + + TGeoTranslation tz(0.0, 0.0, mZPos); + motherVolume->AddNode(layerVol, 1, combiTrans ? combiTrans : &tz); +} + +// ClassImp(VDLayer); +// ClassImp(VDCylindricalLayer); +// ClassImp(VDRectangularLayer); +// ClassImp(VDDiskLayer); + +} // namespace trk +} // namespace o2 \ No newline at end of file diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt index e86ed7982c85b..e3309d78f47ea 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/CMakeLists.txt @@ -13,18 +13,24 @@ o2_add_library(TRKWorkflow TARGETVARNAME targetName SOURCES src/DigitReaderSpec.cxx src/DigitWriterSpec.cxx - src/TrackerSpec.cxx + src/ClustererSpec.cxx + src/ClusterWriterSpec.cxx src/RecoWorkflow.cxx PUBLIC_LINK_LIBRARIES O2::Framework O2::GPUWorkflow O2::SimConfig O2::DataFormatsITSMFT + O2::DataFormatsTRK O2::SimulationDataFormat - O2::DPLUtils) + O2::DPLUtils + O2::TRKBase + O2::TRKSimulation + O2::TRKReconstruction + nlohmann_json::nlohmann_json) o2_add_executable(reco-workflow SOURCES src/trk-reco-workflow.cxx COMPONENT_NAME alice3-trk PUBLIC_LINK_LIBRARIES O2::TRKWorkflow O2::TRKSimulation - O2::ITStracking) \ No newline at end of file + O2::TRKReconstruction) diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/README.md b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md new file mode 100644 index 0000000000000..2afb599319217 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/README.md @@ -0,0 +1,11 @@ +# TRK Reconstruction Workflow + +This workflow handles TRK-local reconstruction devices such as digit reading and clusterization. + +## Basic Command + +```bash +o2-alice3-trk-reco-workflow -b +``` + +Use `o2-alice3-global-reconstruction-reco-workflow` for ALICE 3 tracking from hits. diff --git a/Framework/TestWorkflows/src/o2_sim_tpc.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h similarity index 75% rename from Framework/TestWorkflows/src/o2_sim_tpc.h rename to Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h index e567fe89e0b38..50d823b497bb9 100644 --- a/Framework/TestWorkflows/src/o2_sim_tpc.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClusterWriterSpec.h @@ -9,17 +9,16 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -#ifndef WORKFLOWS_O2_SIM_TPC -#define WORKFLOWS_O2_SIM_TPC +#ifndef O2_TRK_CLUSTERWRITER +#define O2_TRK_CLUSTERWRITER #include "Framework/DataProcessorSpec.h" -namespace o2 +namespace o2::trk { -namespace workflows -{ -o2::framework::DataProcessorSpec sim_tpc(); -} -} // namespace o2 -#endif // WORKFLOWS_O2_SIM_TPC +framework::DataProcessorSpec getClusterWriterSpec(bool useMC); + +} // namespace o2::trk + +#endif diff --git a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h new file mode 100644 index 0000000000000..9d072e85d574a --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/ClustererSpec.h @@ -0,0 +1,48 @@ +// 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_TRK_CLUSTERERDPL +#define O2_TRK_CLUSTERERDPL + +#include "Framework/DataProcessorSpec.h" +#include "Framework/Task.h" +#include "TRKBase/AlmiraParam.h" +#include "TRKReconstruction/Clusterer.h" +#ifdef O2_WITH_ACTS +#include "TRKReconstruction/ClustererACTS.h" +#endif + +namespace o2::trk +{ + +class ClustererDPL : public o2::framework::Task +{ + public: + ClustererDPL(bool useMC) : mUseMC(useMC) {} + void init(o2::framework::InitContext& ic) final; + void run(o2::framework::ProcessingContext& pc) final; + + private: + static constexpr int mLayers = o2::trk::AlmiraParam::kNLayers; + bool mUseMC = true; + int mNThreads = 1; + o2::trk::Clusterer mClusterer; +#ifdef O2_WITH_ACTS + bool mUseACTS = false; + o2::trk::ClustererACTS mClustererACTS; +#endif +}; + +o2::framework::DataProcessorSpec getClustererSpec(bool useMC); + +} // namespace o2::trk + +#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 0c2489aa4b9c4..863c5deae7241 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/include/TRKWorkflow/RecoWorkflow.h @@ -13,7 +13,6 @@ #define O2_TRK_RECOWORKFLOW_H #include "Framework/WorkflowSpec.h" -#include "GPUDataTypes.h" namespace o2::trk { @@ -23,9 +22,7 @@ namespace reco_workflow o2::framework::WorkflowSpec getWorkflow(bool useMC, 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 new file mode 100644 index 0000000000000..863915bac0572 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClusterWriterSpec.cxx @@ -0,0 +1,109 @@ +// 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 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" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" + +using namespace o2::framework; + +namespace o2::trk +{ + +template +using BranchDefinition = MakeRootTreeWriterSpec::BranchDefinition; +using ClustersType = std::vector; +using PatternsType = std::vector; +using ROFrameType = std::vector; +using LabelsType = o2::dataformats::MCTruthContainer; +using ROFRecLblType = std::vector; + +DataProcessorSpec getClusterWriterSpec(bool useMC) +{ + 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 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(std::format("{}-cluster-writer", detNameLC).c_str(), + "o2clus_trk.root", + 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 new file mode 100644 index 0000000000000..f91262e021a55 --- /dev/null +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/ClustererSpec.cxx @@ -0,0 +1,128 @@ +// 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 "TRKWorkflow/ClustererSpec.h" +#include "DetectorsBase/GeometryManager.h" +#include "DataFormatsTRK/Cluster.h" +#include "DataFormatsTRK/ROFRecord.h" +#include "Framework/ConfigParamRegistry.h" +#include "Framework/Logger.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" + +#include + +namespace o2::trk +{ + +void ClustererDPL::init(o2::framework::InitContext& ic) +{ + mNThreads = std::max(1, ic.options().get("nthreads")); +#ifdef O2_WITH_ACTS + mUseACTS = ic.options().get("useACTS"); +#endif +} + +void ClustererDPL::run(o2::framework::ProcessingContext& pc) +{ + o2::base::GeometryManager::loadGeometry("sgn_geometry.root", false, true); + + 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)); + + 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 on layer " << iLayer; + mClustererACTS.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } else +#endif + { + LOG(info) << "Running TRKClusterer on layer " << iLayer; + mClusterer.process(digits, + rofs, + clusters, + patterns, + clusterROFs, + mUseMC ? &labels : nullptr, + clusterLabels.get()); + } + + 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 produced {} clusters", totalClusters); +} + +o2::framework::DataProcessorSpec getClustererSpec(bool useMC) +{ + static constexpr int nLayers = o2::trk::AlmiraParam::kNLayers; + std::vector inputs; + 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; + 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{ + "trk-clusterer", + inputs, + outputs, + o2::framework::AlgorithmSpec{o2::framework::adaptFromTask(useMC)}, + o2::framework::Options{{"nthreads", o2::framework::VariantType::Int, 1, {"Number of clustering threads"}} +#ifdef O2_WITH_ACTS + , + {"useACTS", o2::framework::VariantType::Bool, false, {"Use ACTS for clustering"}} +#endif + }}; +} + +} // namespace o2::trk 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 3b2b44729b259..02895f42ac094 100644 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ALICE3/TRK/workflow/src/RecoWorkflow.cxx @@ -10,22 +10,35 @@ // or submit itself to any jurisdiction. #include "TRKWorkflow/RecoWorkflow.h" -#include "TRKWorkflow/TrackerSpec.h" +#include "TRKWorkflow/ClustererSpec.h" +#include "TRKWorkflow/ClusterWriterSpec.h" +#include "TRKWorkflow/DigitReaderSpec.h" #include "Framework/CCDBParamSpec.h" +#include + namespace o2::trk::reco_workflow { framework::WorkflowSpec getWorkflow(bool useMC, bool upstreamDigits, bool upstreamClusters, - bool disableRootOutput, - bool useGPUWF, - o2::gpu::GPUDataTypes::DeviceType dtype) + bool disableRootOutput) { framework::WorkflowSpec specs; - specs.emplace_back(o2::trk::getTrackerSpec(useMC, dtype)); + + if (!(upstreamDigits || upstreamClusters)) { + specs.emplace_back(o2::trk::getTRKDigitReaderSpec(useMC, false, "trkdigits.root")); + } + if (!upstreamClusters) { + specs.emplace_back(o2::trk::getClustererSpec(useMC)); + } + + if (!disableRootOutput) { + specs.emplace_back(o2::trk::getClusterWriterSpec(useMC)); + } + return specs; } -} // namespace o2::trk::reco_workflow \ No newline at end of file +} // namespace o2::trk::reco_workflow 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 4057bab3b948f..0000000000000 --- a/Detectors/Upgrades/ALICE3/TRK/workflow/src/TrackerSpec.cxx +++ /dev/null @@ -1,116 +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 "Framework/ControlService.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/CCDBParamSpec.h" -#include "TRKWorkflow/TrackerSpec.h" - -namespace o2 -{ -using namespace framework; -namespace trk -{ -using Vertex = o2::dataformats::Vertex>; - -TrackerDPL::TrackerDPL(std::shared_ptr gr, - bool isMC, - o2::gpu::GPUDataTypes::DeviceType dType) -{ - // 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); -} - -void TrackerDPL::run(ProcessingContext& pc) -{ - auto cput = mTimer.CpuTime(); - auto realt = mTimer.RealTime(); - mTimer.Start(false); - // mITSTrackingInterface.updateTimeDependentParams(pc); - // mITSTrackingInterface.run(pc); - 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, o2::gpu::GPUDataTypes::DeviceType dType) -{ - std::vector inputs; - - // 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")); - 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); - std::vector outputs; - outputs.emplace_back("TRK", "TRACKS", 0, Lifetime::Timeframe); - // 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, - 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 0f75d42710400..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,10 +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"}}, - {"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); } @@ -66,8 +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 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"); @@ -76,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, extDigits, extClusters, disableRootOutput, useGpuWF, gpuDevice); + return o2::trk::reco_workflow::getWorkflow(useMC, extDigits, extClusters, disableRootOutput); } diff --git a/Detectors/Upgrades/ITS3/CMakeLists.txt b/Detectors/Upgrades/ITS3/CMakeLists.txt index 73ad4b9d53e37..bdaf1b4bf4292 100644 --- a/Detectors/Upgrades/ITS3/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/CMakeLists.txt @@ -12,10 +12,10 @@ #add_compile_options(-O0 -g -fPIC -fsanitize=address) #add_link_options(-fsanitize=address) -add_subdirectory(data) add_subdirectory(simulation) add_subdirectory(alignment) add_subdirectory(base) add_subdirectory(workflow) add_subdirectory(reconstruction) add_subdirectory(macros) +add_subdirectory(study) 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/Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx b/Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx new file mode 100644 index 0000000000000..0d89cb4d4cffd --- /dev/null +++ b/Detectors/Upgrades/ITS3/alignment/src/AlignmentParams.cxx @@ -0,0 +1,13 @@ +// 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/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 cb6af1dcfc5b7..270b1a7148f61 100644 --- a/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h +++ b/Detectors/Upgrades/ITS3/base/include/ITS3Base/SpecsV2.h @@ -28,9 +28,9 @@ // color: for visulisation namespace o2::its3::constants { -constexpr double cm{1e+2}; // This is the default unit of TGeo so we use this as scale -constexpr double mu{1e-6 * cm}; -constexpr double mm{1e-3 * cm}; +constexpr double cm{1.0}; // This is the default unit of TGeo so we use this as scale +constexpr double mu{1e-4 * cm}; +constexpr double mm{1e-1 * cm}; namespace pixelarray { constexpr double width{9.197 * mm}; @@ -102,13 +102,14 @@ constexpr double lengthSensitive{nRSUs * rsu::length}; namespace carbonfoam { // TODO: Waiting for the further information from WP5(Corrado) -constexpr double longeronsWidth{2.0 * mm}; // what is the height of the longerons? -constexpr double longeronsLength{263 * mm}; // from blueprint constexpr double HringLength{6.0 * mm}; // from blueprint +constexpr double longeronsWidth{2.0 * mm}; // what is the height of the longerons? +constexpr double longeronsLength{segment::length - (2 * HringLength)}; // 263mm from blueprint; overrriden to be consitent constexpr double edgeBetwChipAndFoam{1.0 * mm}; // from blueprint but not used cause forms are already overlapping constexpr double gapBetwHringsLongerons{0.05 * mm}; // from blueprint constexpr std::array nHoles{11, 11, 11}; // how many holes for each layer? -constexpr std::array radiusHoles{1.0 * mm, 1.0 * mm, 2.0 * mm}; // what is the radius of the holes for each layer? +constexpr std::array radiusHoles{1.0 * mm, 1.0 * mm, 2.0 * mm}; // TODO what is the radius of the holes for each layer? +constexpr double thicknessOuterFoam{7 * mm}; // TODO: lack of carbon foam radius for layer 2, use 0.7 cm as a temporary value constexpr EColor color{kGray}; } // namespace carbonfoam namespace metalstack @@ -208,10 +209,22 @@ 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 + +// services +namespace services +{ +// FIXME these value are hallucinated since this not yet defined +constexpr double thickness{2.2 * mm}; // thickness of structure +constexpr double radiusInner{radiiOuter[2] + carbonfoam::thicknessOuterFoam}; // inner radius of services +constexpr double radiusOuter{radiusInner + thickness}; // outer radius of services +constexpr double length{segment::length + (1 * cm)}; // length +constexpr EColor color{kBlue}; +} // namespace services + } // namespace o2::its3::constants #endif diff --git a/Detectors/Upgrades/ITS3/data/CMakeLists.txt b/Detectors/Upgrades/ITS3/data/CMakeLists.txt deleted file mode 100644 index 7a807fd670370..0000000000000 --- a/Detectors/Upgrades/ITS3/data/CMakeLists.txt +++ /dev/null @@ -1,34 +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. - -set(APTS_RESPONSE_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/APTSResponseData.root") - -add_custom_command( - OUTPUT ${APTS_RESPONSE_OUTPUT} - COMMAND ${CMAKE_BINARY_DIR}/stage/bin/o2-alpide-response-generator - -c APTS - -i ${ITSRESPONSE_DIR}/response/ITS3ChipResponseData/AptsResponseData/ - -o ${CMAKE_CURRENT_BINARY_DIR}/ - DEPENDS GenerateAlpideResponse - ${ITSRESPONSE_DIR}/response/ITS3ChipResponseData/AptsResponseData/ - COMMENT "Generating APTSResponseData.root" - VERBATIM -) - -add_custom_target( - GenerateAPTSResponse ALL - DEPENDS ${APTS_RESPONSE_OUTPUT} -) - -install( - FILES ${APTS_RESPONSE_OUTPUT} - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/Detectors/Upgrades/ITS3/data/ITS3ChipResponseData/" -) 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/CheckResidualsITS3.C b/Detectors/Upgrades/ITS3/macros/align/CheckResidualsITS3.C index 9d352393d6fd9..88b342683ca44 100644 --- a/Detectors/Upgrades/ITS3/macros/align/CheckResidualsITS3.C +++ b/Detectors/Upgrades/ITS3/macros/align/CheckResidualsITS3.C @@ -66,16 +66,11 @@ std::optional propagateTo(Track& trk, const o2::itsmft::CompClusterExt& ++cTotal; auto chipID = clus.getSensorID(); float sigmaY2{0}, sigmaZ2{0}, sigmaYZ{0}; + auto isITS3 = o2::its3::constants::detID::isDetITS3(chipID); const float alpha = o2::its::GeometryTGeo::Instance()->getSensorRefAlpha(clus.getSensorID()); // alpha for the tracking frame const auto locC = o2::its3::ioutils::extractClusterData(clus, pattIt, mDict, sigmaY2, sigmaZ2); // get cluster in sensor local frame with errors - Point3D trkC; - auto isITS3 = o2::its3::constants::detID::isDetITS3(chipID); - if (isITS3) { - trkC = o2::its::GeometryTGeo::Instance()->getT2LMatrixITS3(chipID, alpha) ^ (locC); // cluster position in the tracking frame - } else { - trkC = o2::its::GeometryTGeo::Instance()->getMatrixT2L(chipID) ^ (locC); // cluster position in the tracking frame - } - const auto gloC = o2::its::GeometryTGeo::Instance()->getMatrixL2G(chipID)(locC); // global cluster position + Point3D trkC = o2::its::GeometryTGeo::Instance()->getMatrixT2L(chipID) ^ (locC); // cluster position in the tracking frame + const auto gloC = o2::its::GeometryTGeo::Instance()->getMatrixL2G(chipID)(locC); // global cluster position const auto bz = o2::base::Propagator::Instance()->getNominalBz(); // rotate the parameters to the tracking frame then propagate to the clusters'x 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/CMakeLists.txt b/Detectors/Upgrades/ITS3/macros/test/CMakeLists.txt index cb6812445283c..6b274e764f276 100644 --- a/Detectors/Upgrades/ITS3/macros/test/CMakeLists.txt +++ b/Detectors/Upgrades/ITS3/macros/test/CMakeLists.txt @@ -12,7 +12,6 @@ its3_add_macro(CheckDigitsITS3.C) its3_add_macro(CheckClustersITS3.C) its3_add_macro(CheckTracksITS3.C) -its3_add_macro(CheckDCA.C) its3_add_macro(CreateDictionariesITS3.C) its3_add_macro(buildMatBudLUT.C) its3_add_macro(CheckHits.C) diff --git a/Detectors/Upgrades/ITS3/macros/test/CheckChipResponseFile.C b/Detectors/Upgrades/ITS3/macros/test/CheckChipResponseFile.C index 32d5bad87ce21..5bc053c516079 100644 --- a/Detectors/Upgrades/ITS3/macros/test/CheckChipResponseFile.C +++ b/Detectors/Upgrades/ITS3/macros/test/CheckChipResponseFile.C @@ -22,6 +22,7 @@ #include #include +#include "CCDB/BasicCCDBManager.h" #define ENABLE_UPGRADES #include "ITSMFTSimulation/AlpideSimResponse.h" #include "ITS3Simulation/ChipSimResponse.h" @@ -37,16 +38,12 @@ double cm2um(double cm) { return cm * 1e+4; } std::unique_ptr mAlpSimResp0, mAlpSimResp1, mAptSimResp1; -std::unique_ptr loadResponse(const std::string& fileName, const std::string& respName) +std::unique_ptr loadResponse(const std::string& path) { - TFile* f = TFile::Open(fileName.data()); - if (!f) { - std::cerr << fileName << " not found" << std::endl; - return nullptr; - } - auto base = f->Get(respName.c_str()); + auto& cdb = o2::ccdb::BasicCCDBManager::instance(); + o2::itsmft::AlpideSimResponse* base = cdb.get(path); if (!base) { - std::cerr << respName << " not found in " << fileName << std::endl; + std::cerr << path << " not found in " << '\n'; return nullptr; } return std::make_unique(base); @@ -54,24 +51,24 @@ std::unique_ptr loadResponse(const std::string& fileN void LoadRespFunc() { - std::string AptsFile = "$(O2_ROOT)/share/Detectors/Upgrades/ITS3/data/ITS3ChipResponseData/APTSResponseData.root"; - std::string AlpideFile = "$(O2_ROOT)/share/Detectors/ITSMFT/data/AlpideResponseData/AlpideResponseData.root"; + auto& cdb = o2::ccdb::BasicCCDBManager::instance(); + cdb.setURL("https://alice-ccdb.cern.ch/"); std::cout << "=====================\n"; LOGP(info, "ALPIDE Vbb=0V response"); - mAlpSimResp0 = loadResponse(AlpideFile, "response0"); // Vbb=0V + mAlpSimResp0 = loadResponse("ITSMFT/Calib/ALPIDEResponseVbb0"); // Vbb=0V mAlpSimResp0->computeCentreFromData(); mAlpSimResp0->print(); LOGP(info, "Response Centre {}", mAlpSimResp0->getRespCentreDep()); std::cout << "=====================\n"; LOGP(info, "ALPIDE Vbb=-3V response"); - mAlpSimResp1 = loadResponse(AlpideFile, "response1"); // Vbb=-3V + mAlpSimResp1 = loadResponse("ITSMFT/Calib/ALPIDEResponseVbbM3"); // Vbb=-3V mAlpSimResp1->computeCentreFromData(); mAlpSimResp1->print(); LOGP(info, "Response Centre {}", mAlpSimResp1->getRespCentreDep()); std::cout << "=====================\n"; LOGP(info, "APTS response"); - mAptSimResp1 = loadResponse(AptsFile, "response1"); // APTS + mAptSimResp1 = loadResponse("IT3/Calib/APTSResponse"); // APTS mAptSimResp1->computeCentreFromData(); mAptSimResp1->print(); LOGP(info, "Response Centre {}", mAptSimResp1->getRespCentreDep()); diff --git a/Detectors/Upgrades/ITS3/macros/test/CheckClustersITS3.C b/Detectors/Upgrades/ITS3/macros/test/CheckClustersITS3.C index f245a047377ae..5e56321c7676d 100644 --- a/Detectors/Upgrades/ITS3/macros/test/CheckClustersITS3.C +++ b/Detectors/Upgrades/ITS3/macros/test/CheckClustersITS3.C @@ -283,7 +283,7 @@ void CheckClustersITS3(const std::string& clusfile = "o2clus_its.root", nt.Draw("cgy:cgx>>h_cgy_vs_cgx_OB(1000, -50, 50, 1000, -50, 50)", "id >= 3456", "colz"); canvCgXCgY->cd(4); nt.Draw("cgy:cgz>>h_cgy_vs_cgz_OB(1000, -100, 100, 1000, -50, 50)", "id >= 3456", "colz"); - canvCgXCgY->SaveAs("it3clusters_y_vs_x_vs_z.pdf"); + canvCgXCgY->SaveAs("it3clusters_y_vs_x_vs_z.png"); auto canvdXdZ = new TCanvas("canvdXdZ", "", 1600, 800); canvdXdZ->Divide(2, 2); @@ -295,7 +295,7 @@ void CheckClustersITS3(const std::string& clusfile = "o2clus_its.root", nt.Draw("dx:dz>>h_dx_vs_dz_IB_z(1000, -0.01, 0.01, 1000, -0.01, 0.01)", "id < 3456 && abs(cgz) < 2", "colz"); canvdXdZ->cd(4)->SetLogz(); nt.Draw("dx:dz>>h_dx_vs_dz_OB_z(1000, -0.01, 0.01, 1000, -0.01, 0.01)", "id >= 3456 && abs(cgz) < 2", "colz"); - canvdXdZ->SaveAs("it3clusters_dx_vs_dz.pdf"); + canvdXdZ->SaveAs("it3clusters_dx_vs_dz.png"); auto canvCHXZ = new TCanvas("canvCHXZ", "", 1600, 1600); canvCHXZ->Divide(2, 2); @@ -307,7 +307,7 @@ void CheckClustersITS3(const std::string& clusfile = "o2clus_its.root", nt.Draw("(cgz-hgz)*10000:eta>>h_chz_IB(101,-1.4,1.4,101,-50,50)", "id<3456", "prof"); canvCHXZ->cd(4); nt.Draw("(cgz-hgz)*10000:eta>>h_chz_OB(101,-1.4,1.4,101,-50,50)", "id>=3456", "prof"); - canvCgXCgY->SaveAs("it3clusters_xz_eta.pdf"); + canvCgXCgY->SaveAs("it3clusters_xz_eta.png"); auto c1 = new TCanvas("p1", "pullX"); c1->cd(); diff --git a/Detectors/Upgrades/ITS3/macros/test/CheckDCA.C b/Detectors/Upgrades/ITS3/macros/test/CheckDCA.C deleted file mode 100644 index b2872431384f1..0000000000000 --- a/Detectors/Upgrades/ITS3/macros/test/CheckDCA.C +++ /dev/null @@ -1,965 +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 CheckDCA.C -/// \brief Simple macro to check ITS3 impact parameter resolution - -#if !defined(__CLING__) || defined(__ROOTCLING__) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DataFormatsITS/TrackITS.h" -#include "DataFormatsTPC/TrackTPC.h" -#include "DetectorsBase/Propagator.h" -#include "Field/MagneticField.h" -#include "ITSBase/GeometryTGeo.h" -#include "DetectorsBase/Propagator.h" -#include "ReconstructionDataFormats/TrackTPCITS.h" -#include "ReconstructionDataFormats/Vertex.h" -#include "ReconstructionDataFormats/DCA.h" -#include "DataFormatsITSMFT/CompCluster.h" -#include "SimulationDataFormat/MCCompLabel.h" -#include "SimulationDataFormat/MCEventHeader.h" -#include "SimulationDataFormat/MCTrack.h" -#include "SimulationDataFormat/MCTruthContainer.h" -#include "SimulationDataFormat/MCUtils.h" -#include "SimulationDataFormat/TrackReference.h" -#include "Steer/MCKinematicsReader.h" - -#include -#include -#include -#include -#include -#include -#include -#endif - -namespace fs = std::filesystem; - -constexpr auto mMatCorr{o2::base::Propagator::MatCorrType::USEMatCorrNONE}; -constexpr float mMaxStep{2}; - -constexpr float rapMax{0.9}; - -std::vector find_dirs(fs::path const& dir, std::function filter, std::optional> sort = std::nullopt) -{ - std::vector result; - if (fs::exists(dir)) { // Find Dirs matching filter - for (auto const& entry : fs::recursive_directory_iterator(dir, fs::directory_options::follow_directory_symlink)) { - if (fs::is_directory(entry) && filter(entry)) { - result.emplace_back(entry); - } - } - } - if (sort) { // Optionally sort paths - std::sort(result.begin(), result.end(), *sort); - } - return result; -} - -void CheckDCA(const std::string& collisioncontextFileName = "collisioncontext.root", - const std::string& tpcTracksFileName = "tpctracks.root", - const std::string& itsTracksFileName = "o2trac_its.root", - const std::string& itstpcTracksFileName = "o2match_itstpc.root", - const std::string& magFileName = "o2sim_grp.root") -{ - gROOT->SetBatch(); - gStyle->SetOptStat(0); - gStyle->SetPalette(kRainBow); - gStyle->SetPadLeftMargin(0.16); - gStyle->SetPadTickX(1); - gStyle->SetPadTickY(1); - gErrorIgnoreLevel = 2001; // suppress warnings - ProcInfo_t procInfo; - - const int nPtBins = 35; - const int nPtBinsEff = 39; - double ptLimits[nPtBins] = {0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2., 2.2, 2.5, 3., 4., 5., 6., 8., 10., 15., 20.}; - double ptLimitsEff[nPtBinsEff] = {0.05, 0.06, 0.07, 0.08, 0.09, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2., 2.2, 2.5, 3., 4., 5., 6., 8., 10., 15., 20.}; - - const std::regex tf_pattern(R"(tf\d+)"); - auto tf_matcher = [&tf_pattern](fs::path const& p) -> bool { - return std::regex_search(p.string(), tf_pattern); - }; - auto tf_sorter = [&tf_pattern](fs::path const& a, fs::path const& b) -> bool { - const auto &as = a.string(), &bs = b.string(); - std::smatch am, bm; - if (std::regex_search(as, am, tf_pattern) && std::regex_search(bs, bm, tf_pattern)) { - return std::stoi(am.str().substr(2)) < std::stoi(bm.str().substr(2)); - } else { - LOGP(fatal, "TF Regex matching failed"); - return false; - } - }; - - const int nSpecies = 4; - std::array pdgCodes{11, 211, 321, 2212}; - auto fGaus = new TF1("fGaus", "gaus", -200., 200.); - std::map partNames = { - {11, "Electrons"}, - {211, "Pions"}, - {321, "Kaons"}, - {2212, "Protons"}}; - std::map colors{{11, kOrange + 7}, {211, kRed + 1}, {321, kAzure + 4}, {2212, kGreen + 2}}; - /// ITS - std::map hDcaxyResAllLayersITS = { - {11, new TH1F("hDcaxyResElectronsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazResAllLayersITS = { - {11, new TH1F("hDcazResElectronsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hPtResAllLayersITS = { - {11, new TH1F("hPtResElectronsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {211, new TH1F("hPtResPionsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {321, new TH1F("hPtResKaonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hPtResProtonsAllLayersITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}}; - std::map hDcaxyResNoFirstLayerITS = { - {11, new TH1F("hDcaxyResElectronsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazResNoFirstLayerITS = { - {11, new TH1F("hDcazResElectronsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonsNoFirstLayerITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcaxyReskAnyITS = { - {11, new TH1F("hDcaxyResElectronskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazReskAnyITS = { - {11, new TH1F("hDcazResElectronskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonskAnyITS", "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - - std::map hDcaxyVsPtAllLayersITS = { - {11, new TH2F("hDcaxyVsPtElectronsAllLayersITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionsAllLayersITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonsAllLayersITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonsAllLayersITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtAllLayersITS = { - {11, new TH2F("hDcazVsPtElectronsAllLayersITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionsAllLayersITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonsAllLayersITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonsAllLayersITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcaxyVsPhiAllLayersITS = { - {11, new TH2F("hDcaxyVsPhiElectronsAllLayersITS", "ITS Electrons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPhiPionsAllLayersITS", "ITS Pions (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPhiKaonsAllLayersITS", "ITS Kaons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPhiProtonsAllLayersITS", "ITS Protons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}}; - std::map hDcazVsPhiAllLayersITS = { - {11, new TH2F("hDcazVsPhiElectronsAllLayersITS", "ITS Electrons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {211, new TH2F("hDcazVsPhiPionsAllLayersITS", "ITS Pions (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {321, new TH2F("hDcazVsPhiKaonsAllLayersITS", "ITS Kaons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPhiProtonsAllLayersITS", "ITS Protons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}}; - std::map hDcaxyVsPtNoFirstLayerITS = { - {11, new TH2F("hDcaxyVsPtElectronsNoFirstLayerITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionsNoFirstLayerITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonsNoFirstLayerITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonsNoFirstLayerITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtNoFirstLayerITS = { - {11, new TH2F("hDcazVsPtElectronsNoFirstLayerITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionsNoFirstLayerITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonsNoFirstLayerITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonsNoFirstLayerITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtkAnyITS = { - {11, new TH2F("hDcazVsPtElectronskAnyITS ", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionskAnyITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonskAnyITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonskAnyITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcaxyVsPtkAnyITS = { - {11, new TH2F("hDcaxyVsPtElectronskAnyITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionskAnyITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonskAnyITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonskAnyITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDeltaPtVsPtAllLayersITS = { - {11, new TH2F("hDeltaPtVsPtElectronsAllLayersITS", "ITS Electrons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {211, new TH2F("hDeltaPtVsPtPionsAllLayersITS", "ITS Pions;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {321, new TH2F("hDeltaPtVsPtKaonsAllLayersITS", "ITS Kaons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {2212, new TH2F("hDeltaPtVsPtProtonsAllLayersITS", "ITS Protons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}}; - // ITS-TPC - std::map hDcaxyResAllLayersITSTPC = { - {11, new TH1F("hDcaxyResElectronsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazResAllLayersITSTPC = { - {11, new TH1F("hDcazResElectronsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hPtResAllLayersITSTPC = { - {11, new TH1F("hPtResElectronsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {211, new TH1F("hPtResPionsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {321, new TH1F("hPtResKaonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hPtResProtonsAllLayersITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits)}}; - std::map hDcaxyResNoFirstLayerITSTPC = { - {11, new TH1F("hDcaxyResElectronsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazResNoFirstLayerITSTPC = { - {11, new TH1F("hDcazResElectronsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonsNoFirstLayerITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcaxyReskAnyITSTPC = { - {11, new TH1F("hDcaxyResElectronskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcaxyResPionskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcaxyResKaonskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcaxyResProtonskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits)}}; - std::map hDcazReskAnyITSTPC = { - {11, new TH1F("hDcazResElectronskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {211, new TH1F("hDcazResPionskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {321, new TH1F("hDcazResKaonskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}, - {2212, new TH1F("hDcazResProtonskAnyITSTPC", "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits)}}; - - std::map hDcaxyVsPtAllLayersITSTPC = { - {11, new TH2F("hDcaxyVsPtElectronsAllLayersITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionsAllLayersITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonsAllLayersITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonsAllLayersITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtAllLayersITSTPC = { - {11, new TH2F("hDcazVsPtElectronsAllLayersITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionsAllLayersITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonsAllLayersITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonsAllLayersITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcaxyVsPhiAllLayersITSTPC = { - {11, new TH2F("hDcaxyVsPhiElectronsAllLayersITSTPC", "ITS-TPC Electrons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPhiPionsAllLayersITSTPC", "ITS-TPC Pions (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPhiKaonsAllLayersITSTPC", "ITS-TPC Kaons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPhiProtonsAllLayersITSTPC", "ITS-TPC Protons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{xy}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}}; - std::map hDcazVsPhiAllLayersITSTPC = { - {11, new TH2F("hDcazVsPhiElectronsAllLayersITSTPC", "ITS-TPC Electrons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {211, new TH2F("hDcazVsPhiPionsAllLayersITSTPC", "ITS-TPC Pions (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {321, new TH2F("hDcazVsPhiKaonsAllLayersITSTPC", "ITS-TPC Kaons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPhiProtonsAllLayersITSTPC", "ITS-TPC Protons (>2 Gev);#varphi (rad);#sigma(DCA_{#it{z}}) (#mum)", 100, 0.f, 2 * TMath::Pi(), 1000, -500, 500)}}; - std::map hDcaxyVsPtNoFirstLayerITSTPC = { - {11, new TH2F("hDcaxyVsPtElectronsNoFirstLayerITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionsNoFirstLayerITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonsNoFirstLayerITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonsNoFirstLayerITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtNoFirstLayerITSTPC = { - {11, new TH2F("hDcazVsPtElectronsNoFirstLayerITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionsNoFirstLayerITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonsNoFirstLayerITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonsNoFirstLayerITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcazVsPtkAnyITSTPC = { - {11, new TH2F("hDcazVsPtElectronskAnyITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcazVsPtPionskAnyITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcazVsPtKaonskAnyITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcazVsPtProtonskAnyITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDcaxyVsPtkAnyITSTPC = { - {11, new TH2F("hDcaxyVsPtElectronskAnyITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {211, new TH2F("hDcaxyVsPtPionskAnyITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {321, new TH2F("hDcaxyVsPtKaonskAnyITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}, - {2212, new TH2F("hDcaxyVsPtProtonskAnyITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)", nPtBins - 1, ptLimits, 1000, -500, 500)}}; - std::map hDeltaPtVsPtAllLayersITSTPC = { - {11, new TH2F("hDeltaPtVsPtElectronsAllLayersITSTPC", "ITS-TPC Electrons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {211, new TH2F("hDeltaPtVsPtPionsAllLayersITSTPC", "ITS-TPC Pions;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {321, new TH2F("hDeltaPtVsPtKaonsAllLayersITSTPC", "ITS-TPC Kaons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}, - {2212, new TH2F("hDeltaPtVsPtProtonsAllLayersITSTPC", "ITS-TPC Protons;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})", nPtBins - 1, ptLimits, 200, -0.2, 0.2)}}; - - o2::dataformats::VertexBase collision; - o2::dataformats::DCA impactParameter; - - const auto origWD{fs::current_path()}; - const auto tfDirs = find_dirs(fs::current_path(), tf_matcher, tf_sorter); - for (const auto& tfDir : tfDirs) { - LOGP(info, "Analysing {:?}", tfDir.c_str()); - fs::current_path(tfDir); - - // MC Information - o2::steer::MCKinematicsReader mcReader; - if (!mcReader.initFromDigitContext(collisioncontextFileName)) { - LOGP(error, "Cannot init MC reader in {:?}", tfDir.c_str()); - continue; - } - - // Magnetic field and Propagator - float bz{-999}; - static bool initOnce{false}; - if (!initOnce) { - initOnce = true; - o2::base::Propagator::initFieldFromGRP(magFileName); - bz = o2::base::Propagator::Instance()->getNominalBz(); - } - - LOGP(info, "Loading ITS Tracks"); - auto fITSTracks = TFile::Open(itsTracksFileName.c_str(), "READ"); - auto tITSTracks = fITSTracks->Get("o2sim"); - std::vector* itsTracks{nullptr}; - tITSTracks->SetBranchAddress("ITSTrack", &itsTracks); - std::vector* itsTrkLab{nullptr}; - tITSTracks->SetBranchAddress("ITSTrackMCTruth", &itsTrkLab); - - for (Long64_t iEntry{0}; tITSTracks->LoadTree(iEntry) >= 0; ++iEntry) { - tITSTracks->GetEntry(iEntry); - for (size_t iTrk{0}; iTrk < itsTracks->size(); ++iTrk) { - auto trk = itsTracks->at(iTrk); - const auto& lbl = itsTrkLab->at(iTrk); - if (!lbl.isValid()) { - continue; - } - - const auto& mcEvent = mcReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); - const auto& mcTrack = mcReader.getTrack(lbl); - if (!mcTrack->isPrimary() || !(std::abs(mcTrack->GetEta()) < rapMax)) { - continue; - } - auto pdg = std::abs(mcTrack->GetPdgCode()); - if (pdg != 11 && pdg != 211 && pdg != 321 && pdg != 2212) { - continue; - } - - collision.setXYZ(mcEvent.GetX(), mcEvent.GetY(), mcEvent.GetZ()); - if (!o2::base::Propagator::Instance()->propagateToDCA(collision, trk, bz, mMaxStep, mMatCorr, &impactParameter)) { - continue; - } - - auto ptReco = trk.getPt(); - auto ptGen = mcTrack->GetPt(); - auto deltaPt = (1. / ptReco - 1. / ptGen) / (1. / ptGen); - auto dcaXY = impactParameter.getY() * 10000; - auto dcaZ = impactParameter.getZ() * 10000; - auto phiReco = trk.getPhi(); - - if (trk.getNumberOfClusters() == 7) { - hDcaxyVsPtAllLayersITS[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtAllLayersITS[pdg]->Fill(ptGen, dcaZ); - hDeltaPtVsPtAllLayersITS[pdg]->Fill(ptGen, deltaPt); - if (ptGen > 2.) { - hDcaxyVsPhiAllLayersITS[pdg]->Fill(phiReco, dcaXY); - hDcazVsPhiAllLayersITS[pdg]->Fill(phiReco, dcaZ); - } - } else if (!trk.hasHitOnLayer(0)) { - hDcaxyVsPtNoFirstLayerITS[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtNoFirstLayerITS[pdg]->Fill(ptGen, dcaZ); - } else { - hDcaxyVsPtkAnyITS[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtkAnyITS[pdg]->Fill(ptGen, dcaZ); - } - } - } - - LOGP(info, "Loading ITS-TPC Tracks"); - auto fITSTPCTracks = TFile::Open(itstpcTracksFileName.c_str(), "READ"); - auto tITSTPCTracks = fITSTPCTracks->Get("matchTPCITS"); - std::vector* itstpcTracks{nullptr}; - tITSTPCTracks->SetBranchAddress("TPCITS", &itstpcTracks); - std::vector* itstpcTrkLab{nullptr}; - tITSTPCTracks->SetBranchAddress("MatchMCTruth", &itstpcTrkLab); - // TPC Tracks - auto fTPCTracks = TFile::Open(tpcTracksFileName.c_str(), "READ"); - auto tTPCTracks = fTPCTracks->Get("tpcrec"); - std::vector* tpcTracks{nullptr}; - tTPCTracks->SetBranchAddress("TPCTracks", &tpcTracks); - std::vector* tpcTrkLab{nullptr}; - tTPCTracks->SetBranchAddress("TPCTracksMCTruth", &tpcTrkLab); - for (Long64_t iEntry{0}; tITSTPCTracks->LoadTree(iEntry) >= 0; ++iEntry) { - tITSTPCTracks->GetEntry(iEntry); - tITSTracks->GetEntry(iEntry); - tTPCTracks->GetEntry(iEntry); - for (size_t iTrk{0}; iTrk < itstpcTracks->size(); ++iTrk) { - auto trk = itstpcTracks->at(iTrk); - const auto& lbl = itstpcTrkLab->at(iTrk); - - const auto& trkITS = itsTracks->at(trk.getRefITS().getIndex()); - const auto& trkITSLbl = itsTrkLab->at(trk.getRefITS().getIndex()); - const auto& trkTPC = tpcTracks->at(trk.getRefTPC().getIndex()); - const auto& trkTPCLbl = tpcTrkLab->at(trk.getRefTPC().getIndex()); - if (!lbl.isValid() || trkITSLbl != trkTPCLbl) { - continue; - } - - const auto& mcEvent = mcReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); - const auto& mcTrack = mcReader.getTrack(lbl); - if (!mcTrack->isPrimary() || !(std::abs(mcTrack->GetEta()) < rapMax)) { - continue; - } - - auto pdg = std::abs(mcTrack->GetPdgCode()); - if (pdg != 11 && pdg != 211 && pdg != 321 && pdg != 2212) { - continue; - } - - collision.setXYZ(mcEvent.GetX(), mcEvent.GetY(), mcEvent.GetZ()); - if (!o2::base::Propagator::Instance()->propagateToDCA(collision, trk, bz, mMaxStep, mMatCorr, &impactParameter)) { - continue; - } - - auto ptReco = trk.getPt(); - auto ptGen = mcTrack->GetPt(); - auto deltaPt = (1. / ptReco - 1. / ptGen) / (1. / ptGen); - auto dcaXY = impactParameter.getY() * 10000; - auto dcaZ = impactParameter.getZ() * 10000; - auto phiReco = trk.getPhi(); - - if (trkITS.getNumberOfClusters() == 7) { - hDcaxyVsPtAllLayersITSTPC[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtAllLayersITSTPC[pdg]->Fill(ptGen, dcaZ); - hDeltaPtVsPtAllLayersITSTPC[pdg]->Fill(ptGen, deltaPt); - if (ptGen > 2.) { - hDcaxyVsPhiAllLayersITSTPC[pdg]->Fill(phiReco, dcaXY); - hDcazVsPhiAllLayersITSTPC[pdg]->Fill(phiReco, dcaZ); - } - } else if (!trkITS.hasHitOnLayer(0) && !trkITS.hasHitOnLayer(1) && !trkITS.hasHitOnLayer(2)) { - hDcaxyVsPtNoFirstLayerITSTPC[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtNoFirstLayerITSTPC[pdg]->Fill(ptGen, dcaZ); - } else { - hDcaxyVsPtkAnyITSTPC[pdg]->Fill(ptGen, dcaXY); - hDcazVsPtkAnyITSTPC[pdg]->Fill(ptGen, dcaZ); - } - } - } - - delete itsTracks; - delete itsTrkLab; - delete tpcTracks; - delete tpcTrkLab; - delete itstpcTracks; - delete itstpcTrkLab; - delete tITSTracks; - delete tTPCTracks; - delete tITSTPCTracks; - delete fITSTracks; - delete fTPCTracks; - delete fITSTPCTracks; - - gSystem->GetProcInfo(&procInfo); - LOGF(info, "MemVirtual (%ld), MemResident (%ld)", procInfo.fMemVirtual, procInfo.fMemResident); - LOGP(info, "Done with {:?}", tfDir.c_str()); - if (procInfo.fMemResident > 200'000'000) { - LOGP(error, "Exceeding 200GBs stopping!"); - break; - } - } - LOGP(info, "Restoring original CWD to {:?}", origWD.c_str()); - fs::current_path(origWD); // restore original wd - - LOGP(info, "Projecting Plots"); - TH1* hProj; - const char* fitOpt{"QWMER"}; - /* const char* fitOpt{"Q"}; */ - std::map lProjITS = { - {11, new TList()}, - {211, new TList()}, - {321, new TList()}, - {2212, new TList()}, - }; - std::map lProjITSTPC = { - {11, new TList()}, - {211, new TList()}, - {321, new TList()}, - {2212, new TList()}, - }; - for (const auto& pdgCode : pdgCodes) { - for (auto iPt{0}; iPt < nPtBins; ++iPt) { - // ITS - auto ptMin = hDcaxyVsPtAllLayersITS[pdgCode]->GetXaxis()->GetBinLowEdge(iPt + 1); - float minFit = (ptMin < 1.) ? -200. : -50.; - float maxFit = (ptMin < 1.) ? 200. : 50.; - - hProj = hDeltaPtVsPtAllLayersITS[pdgCode]->ProjectionY(Form("hProjDeltaPtAll%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt); - lProjITS[pdgCode]->Add(hProj); - hPtResAllLayersITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hPtResAllLayersITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcaxyVsPtAllLayersITS[pdgCode]->ProjectionY(Form("hProjDcaxyAll%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcaxyResAllLayersITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyResAllLayersITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtAllLayersITS[pdgCode]->ProjectionY(Form("hProjDcazAll%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcazResAllLayersITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazResAllLayersITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcaxyVsPtNoFirstLayerITS[pdgCode]->ProjectionY(Form("hProjDcaxyNoFirst%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcaxyResNoFirstLayerITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyResNoFirstLayerITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtNoFirstLayerITS[pdgCode]->ProjectionY(Form("hProjDcazNoFirst%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcazResNoFirstLayerITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazResNoFirstLayerITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcaxyVsPtkAnyITS[pdgCode]->ProjectionY(Form("hProjDcaxyAny%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcaxyReskAnyITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyReskAnyITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtkAnyITS[pdgCode]->ProjectionY(Form("hProjDcazAny%d__%dITS", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITS[pdgCode]->Add(hProj); - hDcazReskAnyITS[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazReskAnyITS[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - // ITS-TPC - hProj = hDeltaPtVsPtAllLayersITSTPC[pdgCode]->ProjectionY(Form("hProjDeltaPtAll%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt); - lProjITSTPC[pdgCode]->Add(hProj); - hPtResAllLayersITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hPtResAllLayersITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - ptMin = hDcaxyVsPtAllLayersITSTPC[pdgCode]->GetXaxis()->GetBinLowEdge(iPt + 1); - minFit = (ptMin < 1.) ? -200. : -50.; - maxFit = (ptMin < 1.) ? 200. : 50.; - - hProj = hDcaxyVsPtAllLayersITSTPC[pdgCode]->ProjectionY(Form("hProjDcaxyAll%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcaxyResAllLayersITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyResAllLayersITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtAllLayersITSTPC[pdgCode]->ProjectionY(Form("hProjDcazAll%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcazResAllLayersITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazResAllLayersITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcaxyVsPtNoFirstLayerITSTPC[pdgCode]->ProjectionY(Form("hProjDcaxyNoFirst%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtNoFirstLayerITSTPC[pdgCode]->ProjectionY(Form("hProjDcazNoFirst%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcazResNoFirstLayerITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazResNoFirstLayerITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcaxyVsPtkAnyITSTPC[pdgCode]->ProjectionY(Form("hProjDcaxAnty%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcaxyReskAnyITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcaxyReskAnyITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - - hProj = hDcazVsPtkAnyITSTPC[pdgCode]->ProjectionY(Form("hProjDcazAny%d__%dITSTPC", pdgCode, iPt), iPt + 1, iPt + 1); - hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); - lProjITSTPC[pdgCode]->Add(hProj); - hDcazReskAnyITSTPC[pdgCode]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); - hDcazReskAnyITSTPC[pdgCode]->SetBinError(iPt + 1, fGaus->GetParError(2)); - } - } - - // Style - LOGP(info, "Styling Plots"); - for (const auto& pdgCode : pdgCodes) { - // ITS - hPtResAllLayersITS[pdgCode]->SetLineWidth(2); - hPtResAllLayersITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hPtResAllLayersITS[pdgCode]->SetLineColor(colors[pdgCode]); - hPtResAllLayersITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcaxyResAllLayersITS[pdgCode]->SetLineWidth(2); - hDcaxyResAllLayersITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyResAllLayersITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyResAllLayersITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcazResAllLayersITS[pdgCode]->SetLineWidth(2); - hDcazResAllLayersITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazResAllLayersITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazResAllLayersITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcaxyResNoFirstLayerITS[pdgCode]->SetLineWidth(2); - hDcaxyResNoFirstLayerITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyResNoFirstLayerITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyResNoFirstLayerITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcazResNoFirstLayerITS[pdgCode]->SetLineWidth(2); - hDcazResNoFirstLayerITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazResNoFirstLayerITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazResNoFirstLayerITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcaxyReskAnyITS[pdgCode]->SetLineWidth(2); - hDcaxyReskAnyITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyReskAnyITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyReskAnyITS[pdgCode]->SetMarkerStyle(kFullCircle); - - hDcazReskAnyITS[pdgCode]->SetLineWidth(2); - hDcazReskAnyITS[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazReskAnyITS[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazReskAnyITS[pdgCode]->SetMarkerStyle(kFullCircle); - - // ITS-TPC - hPtResAllLayersITSTPC[pdgCode]->SetLineWidth(2); - hPtResAllLayersITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hPtResAllLayersITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hPtResAllLayersITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcaxyResAllLayersITSTPC[pdgCode]->SetLineWidth(2); - hDcaxyResAllLayersITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyResAllLayersITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyResAllLayersITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcazResAllLayersITSTPC[pdgCode]->SetLineWidth(2); - hDcazResAllLayersITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazResAllLayersITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazResAllLayersITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetLineWidth(2); - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyResNoFirstLayerITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcazResNoFirstLayerITSTPC[pdgCode]->SetLineWidth(2); - hDcazResNoFirstLayerITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazResNoFirstLayerITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazResNoFirstLayerITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcaxyReskAnyITSTPC[pdgCode]->SetLineWidth(2); - hDcaxyReskAnyITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcaxyReskAnyITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcaxyReskAnyITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - - hDcazReskAnyITSTPC[pdgCode]->SetLineWidth(2); - hDcazReskAnyITSTPC[pdgCode]->SetMarkerColor(colors[pdgCode]); - hDcazReskAnyITSTPC[pdgCode]->SetLineColor(colors[pdgCode]); - hDcazReskAnyITSTPC[pdgCode]->SetMarkerStyle(kOpenCircle); - } - - /// Output - LOGP(info, "Writing final output"); - // ITS - auto canvPtDeltaITS = new TCanvas("canvPtDeltaITS", "", 1500, 500); - canvPtDeltaITS->Divide(nSpecies, 1); - canvPtDeltaITS->cd(1)->SetLogz(); - hDeltaPtVsPtAllLayersITS[11]->Draw("colz"); - canvPtDeltaITS->cd(2)->SetLogz(); - hDeltaPtVsPtAllLayersITS[211]->Draw("colz"); - canvPtDeltaITS->cd(3)->SetLogz(); - hDeltaPtVsPtAllLayersITS[321]->Draw("colz"); - canvPtDeltaITS->cd(4)->SetLogz(); - hDeltaPtVsPtAllLayersITS[2212]->Draw("colz"); - - auto canvDcaVsPtITS = new TCanvas("canvDcaVsPtITS", "", 1500, 1000); - canvDcaVsPtITS->Divide(nSpecies, 2); - canvDcaVsPtITS->cd(1)->SetLogz(); - hDcaxyVsPtAllLayersITS[11]->Draw("colz"); - canvDcaVsPtITS->cd(2)->SetLogz(); - hDcaxyVsPtAllLayersITS[211]->Draw("colz"); - canvDcaVsPtITS->cd(3)->SetLogz(); - hDcaxyVsPtAllLayersITS[321]->Draw("colz"); - canvDcaVsPtITS->cd(4)->SetLogz(); - hDcaxyVsPtAllLayersITS[2212]->Draw("colz"); - canvDcaVsPtITS->cd(5)->SetLogz(); - hDcazVsPtAllLayersITS[11]->Draw("colz"); - canvDcaVsPtITS->cd(6)->SetLogz(); - hDcazVsPtAllLayersITS[211]->Draw("colz"); - canvDcaVsPtITS->cd(7)->SetLogz(); - hDcazVsPtAllLayersITS[321]->Draw("colz"); - canvDcaVsPtITS->cd(8)->SetLogz(); - hDcazVsPtAllLayersITS[2212]->Draw("colz"); - - auto canvPtResITS = new TCanvas("canvPtResITS", "", 500, 500); - canvPtResITS->DrawFrame(ptLimits[0], 0., ptLimits[nPtBins - 1], 0.2, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})"); - for (const auto& pdgCode : pdgCodes) { - hPtResAllLayersITS[pdgCode]->Draw("same"); - } - - auto canvDcaxyResITS = new TCanvas("canvDcaxyResITS", "", 500, 500); - canvDcaxyResITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyResITS->SetLogx(); - canvDcaxyResITS->SetLogy(); - canvDcaxyResITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyResAllLayersITS[pdgCode]->Draw("same"); - } - - auto canvDcazResITS = new TCanvas("canvDcazResITS", "", 500, 500); - canvDcazResITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazResITS->SetLogx(); - canvDcazResITS->SetLogy(); - canvDcazResITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazResAllLayersITS[pdgCode]->Draw("same"); - } - - auto canvDcaxyResNoFirstLayerITS = new TCanvas("canvDcaxyResNoFirstLayerITS", "", 500, 500); - canvDcaxyResNoFirstLayerITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyResNoFirstLayerITS->SetLogx(); - canvDcaxyResNoFirstLayerITS->SetLogy(); - canvDcaxyResNoFirstLayerITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyResNoFirstLayerITS[pdgCode]->Draw("same"); - } - - auto canvDcazResNoFirstLayerITS = new TCanvas("canvDcazResNoFirstLayerITS", "", 500, 500); - canvDcazResNoFirstLayerITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazResNoFirstLayerITS->SetLogx(); - canvDcazResNoFirstLayerITS->SetLogy(); - canvDcazResNoFirstLayerITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazResNoFirstLayerITS[pdgCode]->Draw("same"); - } - - auto canvDcaxyReskAnyITS = new TCanvas("canvDcaxyReskAnyITS", "", 500, 500); - canvDcaxyReskAnyITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyReskAnyITS->SetLogx(); - canvDcaxyReskAnyITS->SetLogy(); - canvDcaxyReskAnyITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyReskAnyITS[pdgCode]->Draw("same"); - } - - auto canvDcazReskAnyITS = new TCanvas("canvDcazReskAnyITS", "", 500, 500); - canvDcazReskAnyITS->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazReskAnyITS->SetLogx(); - canvDcazReskAnyITS->SetLogy(); - canvDcazReskAnyITS->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazReskAnyITS[pdgCode]->Draw("same"); - } - - // ITS-TPC - auto canvPtDeltaITSTPC = new TCanvas("canvPtDeltaITSTPC", "", 1500, 500); - canvPtDeltaITSTPC->Divide(nSpecies, 1); - canvPtDeltaITSTPC->cd(1)->SetLogz(); - hDeltaPtVsPtAllLayersITSTPC[11]->Draw("colz"); - canvPtDeltaITSTPC->cd(2)->SetLogz(); - hDeltaPtVsPtAllLayersITSTPC[211]->Draw("colz"); - canvPtDeltaITSTPC->cd(3)->SetLogz(); - hDeltaPtVsPtAllLayersITSTPC[321]->Draw("colz"); - canvPtDeltaITSTPC->cd(4)->SetLogz(); - hDeltaPtVsPtAllLayersITSTPC[2212]->Draw("colz"); - - auto canvDcaVsPtITSTPC = new TCanvas("canvDcaVsPtITSTPC", "", 1500, 1000); - canvDcaVsPtITSTPC->Divide(nSpecies, 2); - canvDcaVsPtITSTPC->cd(1)->SetLogz(); - hDcaxyVsPtAllLayersITSTPC[11]->Draw("colz"); - canvDcaVsPtITSTPC->cd(2)->SetLogz(); - hDcaxyVsPtAllLayersITSTPC[211]->Draw("colz"); - canvDcaVsPtITSTPC->cd(3)->SetLogz(); - hDcaxyVsPtAllLayersITSTPC[321]->Draw("colz"); - canvDcaVsPtITSTPC->cd(4)->SetLogz(); - hDcaxyVsPtAllLayersITSTPC[2212]->Draw("colz"); - canvDcaVsPtITSTPC->cd(5)->SetLogz(); - hDcazVsPtAllLayersITSTPC[11]->Draw("colz"); - canvDcaVsPtITSTPC->cd(6)->SetLogz(); - hDcazVsPtAllLayersITSTPC[211]->Draw("colz"); - canvDcaVsPtITSTPC->cd(7)->SetLogz(); - hDcazVsPtAllLayersITSTPC[321]->Draw("colz"); - canvDcaVsPtITSTPC->cd(8)->SetLogz(); - hDcazVsPtAllLayersITSTPC[2212]->Draw("colz"); - - auto canvPtResITSTPC = new TCanvas("canvPtResITSTPC", "", 500, 500); - canvPtResITSTPC->DrawFrame(ptLimits[0], 0., ptLimits[nPtBins - 1], 0.2, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})"); - for (const auto& pdgCode : pdgCodes) { - hPtResAllLayersITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcaxyResITSTPC = new TCanvas("canvDcaxyResITSTPC", "", 500, 500); - canvDcaxyResITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyResITSTPC->SetLogx(); - canvDcaxyResITSTPC->SetLogy(); - canvDcaxyResITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyResAllLayersITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcazResITSTPC = new TCanvas("canvDcazResITSTPC", "", 500, 500); - canvDcazResITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazResITSTPC->SetLogx(); - canvDcazResITSTPC->SetLogy(); - canvDcazResITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazResAllLayersITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcaxyResNoFirstLayerITSTPC = new TCanvas("canvDcaxyResNoFirstLayerITSTPC", "", 500, 500); - canvDcaxyResNoFirstLayerITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyResNoFirstLayerITSTPC->SetLogx(); - canvDcaxyResNoFirstLayerITSTPC->SetLogy(); - canvDcaxyResNoFirstLayerITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyResNoFirstLayerITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcazResNoFirstLayerITSTPC = new TCanvas("canvDcazResNoFirstLayerITSTPC", "", 500, 500); - canvDcazResNoFirstLayerITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazResNoFirstLayerITSTPC->SetLogx(); - canvDcazResNoFirstLayerITSTPC->SetLogy(); - canvDcazResNoFirstLayerITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazResNoFirstLayerITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcaxyReskAnyITSTPC = new TCanvas("canvDcaxyReskAnyITSTPC", "", 500, 500); - canvDcaxyReskAnyITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyReskAnyITSTPC->SetLogx(); - canvDcaxyReskAnyITSTPC->SetLogy(); - canvDcaxyReskAnyITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcaxyReskAnyITSTPC[pdgCode]->Draw("same"); - } - - auto canvDcazReskAnyITSTPC = new TCanvas("canvDcazReskAnyITSTPC", "", 500, 500); - canvDcazReskAnyITSTPC->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS-TPC;#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazReskAnyITSTPC->SetLogx(); - canvDcazReskAnyITSTPC->SetLogy(); - canvDcazReskAnyITSTPC->SetGrid(); - for (const auto& pdgCode : pdgCodes) { - hDcazReskAnyITSTPC[pdgCode]->Draw("same"); - } - - // Compare ITS-TPC resolution; - auto canvDcaxyResComp = new TCanvas("canvDcaxyResAllLayersComp", "", 500, 500); - canvDcaxyResComp->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS vs. ITS-TPC (all layers);#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum)"); - canvDcaxyResComp->SetLogx(); - canvDcaxyResComp->SetLogy(); - canvDcaxyResComp->SetGrid(); - hDcaxyResAllLayersITS[211]->Draw("same"); - hDcaxyResAllLayersITSTPC[211]->Draw("same"); - gPad->BuildLegend(0.8, 0.8, 0.94, 0.94); - - auto canvDcazResComp = new TCanvas("canvDcazResAllLayersComp", "", 500, 500); - canvDcazResComp->DrawFrame(ptLimits[0], 1., ptLimits[nPtBins - 1], 1.e3, "ITS vs. ITS-TPC (all layers);#it{p}_{T} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum)"); - canvDcazResComp->SetLogx(); - canvDcazResComp->SetLogy(); - canvDcazResComp->SetGrid(); - hDcazResAllLayersITS[211]->Draw("same"); - hDcazResAllLayersITSTPC[211]->Draw("same"); - gPad->BuildLegend(0.8, 0.8, 0.94, 0.94); - - auto canvPtResComp = new TCanvas("canvPtResAllLayersComp", "", 500, 500); - canvPtResComp->DrawFrame(ptLimits[0], 0., ptLimits[nPtBins - 1], 0.2, "ITS vs. ITS-TPC (all layers);#it{p}_{T} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T})"); - canvPtResComp->SetLogx(); - canvPtResComp->SetGrid(); - hPtResAllLayersITS[211]->Draw("same"); - hPtResAllLayersITSTPC[211]->Draw("same"); - gPad->BuildLegend(0.8, 0.8, 0.94, 0.94); - - auto canvDcaPtComp = new TCanvas("canvDcaPtAllLayersComp", "", 500, 500); - canvDcaPtComp->Divide(2, 2); - canvDcaPtComp->cd(1); - hDcaxyVsPtAllLayersITS[211]->Draw(); - canvDcaPtComp->cd(2); - hDcazVsPtAllLayersITS[211]->Draw(); - canvDcaPtComp->cd(3); - hDcaxyVsPtAllLayersITSTPC[211]->Draw(); - canvDcaPtComp->cd(4); - hDcazVsPtAllLayersITSTPC[211]->Draw(); - - auto canvDcaPhiComp = new TCanvas("canvDcaPhiAllLayersComp", "", 500, 500); - canvDcaPhiComp->Divide(2, 2); - canvDcaPhiComp->cd(1); - hDcaxyVsPhiAllLayersITS[211]->Draw(); - canvDcaPhiComp->cd(2); - hDcazVsPhiAllLayersITS[211]->Draw(); - canvDcaPhiComp->cd(3); - hDcaxyVsPhiAllLayersITSTPC[211]->Draw(); - canvDcaPhiComp->cd(4); - hDcazVsPhiAllLayersITSTPC[211]->Draw(); - - // Write - TFile outFile("checkDCA.root", "RECREATE"); - outFile.mkdir("ITS"); - outFile.cd("ITS"); - gDirectory->WriteTObject(canvPtResITS); - gDirectory->WriteTObject(canvDcaxyResITS); - gDirectory->WriteTObject(canvDcazResITS); - gDirectory->WriteTObject(canvDcazResNoFirstLayerITS); - gDirectory->WriteTObject(canvDcaxyResNoFirstLayerITS); - gDirectory->WriteTObject(canvDcaxyReskAnyITS); - gDirectory->WriteTObject(canvDcazReskAnyITS); - gDirectory->WriteTObject(canvPtDeltaITS); - gDirectory->WriteTObject(canvDcaVsPtITS); - - outFile.mkdir("ITS-TPC"); - outFile.cd("ITS-TPC"); - gDirectory->WriteTObject(canvPtResITSTPC); - gDirectory->WriteTObject(canvDcaxyResITSTPC); - gDirectory->WriteTObject(canvDcazResITSTPC); - gDirectory->WriteTObject(canvDcazResNoFirstLayerITSTPC); - gDirectory->WriteTObject(canvDcaxyResNoFirstLayerITSTPC); - gDirectory->WriteTObject(canvDcaxyReskAnyITSTPC); - gDirectory->WriteTObject(canvDcazReskAnyITSTPC); - gDirectory->WriteTObject(canvPtDeltaITSTPC); - gDirectory->WriteTObject(canvDcaVsPtITSTPC); - - outFile.mkdir("Compare"); - outFile.cd("Compare"); - gDirectory->WriteTObject(canvDcaxyResComp); - gDirectory->WriteTObject(canvDcazResComp); - gDirectory->WriteTObject(canvPtResComp); - gDirectory->WriteTObject(canvDcaPtComp); - gDirectory->WriteTObject(canvDcaPhiComp); - - for (const auto& pdgCode : pdgCodes) { - const char* dirName = partNames[pdgCode].c_str(); - auto dir = outFile.mkdir(dirName); - outFile.cd(dirName); - - gDirectory->mkdir("ITS"); - gDirectory->cd("ITS"); - gDirectory->WriteTObject(hDeltaPtVsPtAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPtAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcazVsPtAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcazResAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcaxyResAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPtNoFirstLayerITS[pdgCode]); - gDirectory->WriteTObject(hDcazVsPtNoFirstLayerITS[pdgCode]); - gDirectory->WriteTObject(hDcazResNoFirstLayerITS[pdgCode]); - gDirectory->WriteTObject(hDcaxyResNoFirstLayerITS[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPhiAllLayersITS[pdgCode]); - gDirectory->WriteTObject(hDcazVsPhiAllLayersITS[pdgCode]); - gDirectory->mkdir("projections"); - gDirectory->cd("projections"); - for (TObject* obj : *lProjITS[pdgCode]) { - obj->Write(); - } - - dir->cd(); - gDirectory->mkdir("ITS-TPC"); - gDirectory->cd("ITS-TPC"); - gDirectory->WriteTObject(hDeltaPtVsPtAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPtAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcazVsPtAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcazResAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcaxyResAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPtNoFirstLayerITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcazVsPtNoFirstLayerITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcazResNoFirstLayerITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcaxyResNoFirstLayerITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcaxyVsPhiAllLayersITSTPC[pdgCode]); - gDirectory->WriteTObject(hDcazVsPhiAllLayersITSTPC[pdgCode]); - gDirectory->mkdir("projections"); - gDirectory->cd("projections"); - for (TObject* obj : *lProjITSTPC[pdgCode]) { - obj->Write(); - } - } -} diff --git a/Detectors/Upgrades/ITS3/macros/test/CheckDigitsITS3.C b/Detectors/Upgrades/ITS3/macros/test/CheckDigitsITS3.C index 240b1bd344af5..82578cc406f0c 100644 --- a/Detectors/Upgrades/ITS3/macros/test/CheckDigitsITS3.C +++ b/Detectors/Upgrades/ITS3/macros/test/CheckDigitsITS3.C @@ -40,7 +40,7 @@ #include "fairlogger/Logger.h" #endif -void CheckDigitsITS3(std::string digifile = "it3digits.root", std::string hitfile = "o2sim_HitsIT3.root", std::string inputGeom = "", bool batch = false) +void CheckDigitsITS3(bool readFromFile = false, std::string digifile = "it3digits.root", std::string hitfile = "o2sim_HitsIT3.root", std::string inputGeom = "", bool batch = false) { gROOT->SetBatch(batch); gStyle->SetPalette(kRainBow); @@ -53,176 +53,211 @@ void CheckDigitsITS3(std::string digifile = "it3digits.root", std::string hitfil using o2::itsmft::SegmentationAlpide; std::array mMosaixSegmentations{0, 1, 2}; - TFile* f = TFile::Open("CheckDigits.root", "recreate"); - TNtuple* nt = new TNtuple("ntd", "digit ntuple", "id:x:y:z:rowD:colD:rowH:colH:xlH:zlH:xlcH:zlcH:dx:dz"); + TFile* f{nullptr}; + TNtuple* nt{nullptr}; + if (!readFromFile) { + f = TFile::Open("CheckDigits.root", "recreate"); + nt = new TNtuple("ntd", "digit ntuple", "id:x:y:z:rowD:colD:rowH:colH:xlH:zlH:xlcH:zlcH:dx:dz:etaH"); - // Geometry - o2::base::GeometryManager::loadGeometry(inputGeom); - auto* gman = o2::its::GeometryTGeo::Instance(); - gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + // Geometry + o2::base::GeometryManager::loadGeometry(inputGeom); + auto* gman = o2::its::GeometryTGeo::Instance(); + gman->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); - // Hits - TFile* hitFile = TFile::Open(hitfile.data()); - TTree* hitTree = (TTree*)hitFile->Get("o2sim"); - int nevH = hitTree->GetEntries(); // hits are stored as one event per entry - std::vector*> hitArray(nevH, nullptr); - std::vector> mc2hitVec(nevH); + // Hits + TFile* hitFile = TFile::Open(hitfile.data()); + TTree* hitTree = (TTree*)hitFile->Get("o2sim"); + int nevH = hitTree->GetEntries(); // hits are stored as one event per entry + std::vector*> hitArray(nevH, nullptr); + std::vector> mc2hitVec(nevH); - // Digits - TFile* digFile = TFile::Open(digifile.data()); - TTree* digTree = (TTree*)digFile->Get("o2sim"); + // Digits + TFile* digFile = TFile::Open(digifile.data()); + TTree* digTree = (TTree*)digFile->Get("o2sim"); - std::vector* digArr = nullptr; - digTree->SetBranchAddress("IT3Digit", &digArr); + std::vector* digArr = nullptr; + digTree->SetBranchAddress("IT3Digit", &digArr); - o2::dataformats::IOMCTruthContainerView* plabels = nullptr; - digTree->SetBranchAddress("IT3DigitMCTruth", &plabels); + o2::dataformats::IOMCTruthContainerView* plabels = nullptr; + digTree->SetBranchAddress("IT3DigitMCTruth", &plabels); - int nevD = digTree->GetEntries(); // digits in cont. readout may be grouped as few events per entry + int nevD = digTree->GetEntries(); // digits in cont. readout may be grouped as few events per entry - int nDigitReadIB{0}, nDigitReadOB{0}; - int nDigitFilledIB{0}, nDigitFilledOB{0}; + int nDigitReadIB{0}, nDigitReadOB{0}; + int nDigitFilledIB{0}, nDigitFilledOB{0}; - // Get Read Out Frame arrays - std::vector* ROFRecordArrray = nullptr; - digTree->SetBranchAddress("IT3DigitROF", &ROFRecordArrray); - std::vector& ROFRecordArrrayRef = *ROFRecordArrray; + // Get Read Out Frame arrays + std::vector* ROFRecordArrray = nullptr; + digTree->SetBranchAddress("IT3DigitROF", &ROFRecordArrray); + std::vector& ROFRecordArrrayRef = *ROFRecordArrray; - std::vector* MC2ROFRecordArrray = nullptr; - digTree->SetBranchAddress("IT3DigitMC2ROF", &MC2ROFRecordArrray); - std::vector& MC2ROFRecordArrrayRef = *MC2ROFRecordArrray; + std::vector* MC2ROFRecordArrray = nullptr; + digTree->SetBranchAddress("IT3DigitMC2ROF", &MC2ROFRecordArrray); + std::vector& MC2ROFRecordArrrayRef = *MC2ROFRecordArrray; - digTree->GetEntry(0); + 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; + 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; - LOGP(debug, "Build min and max MC events used by each ROF"); - for (int imc = MC2ROFRecordArrrayRef.size(); imc--;) { - const auto& mc2rof = MC2ROFRecordArrrayRef[imc]; - /* LOGP(debug, "MCRecord: {}", mc2rof.asString()); */ + LOGP(debug, "Build min and max MC events used by each ROF"); + for (int imc = MC2ROFRecordArrrayRef.size(); imc--;) { + const auto& mc2rof = MC2ROFRecordArrrayRef[imc]; + /* LOGP(debug, "MCRecord: {}", mc2rof.asString()); */ - if (mc2rof.rofRecordID < 0) { - continue; // this MC event did not contribute to any ROF - } + if (mc2rof.rofRecordID < 0) { + continue; // this MC event did not contribute to any ROF + } - for (int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { + for (int irfd = mc2rof.maxROF - mc2rof.minROF + 1; irfd--;) { - int irof = mc2rof.rofRecordID + irfd; + int irof = mc2rof.rofRecordID + irfd; - if (irof >= nROFRec) { - LOGP(error, "ROF={} from MC2ROF record is >= N ROFs={}", irof, nROFRec); - } - if (mcEvMin[irof] > imc) { - mcEvMin[irof] = imc; - } - if (mcEvMax[irof] < imc) { - mcEvMax[irof] = imc; + if (irof >= nROFRec) { + LOGP(error, "ROF={} from MC2ROF record is >= N ROFs={}", irof, nROFRec); + } + if (mcEvMin[irof] > imc) { + mcEvMin[irof] = imc; + } + if (mcEvMax[irof] < imc) { + mcEvMax[irof] = imc; + } } } - } - LOGP(debug, "LOOP on: ROFRecord array"); - unsigned int rofIndex = 0; - unsigned int rofNEntries = 0; - for (unsigned int iROF = 0; iROF < ROFRecordArrrayRef.size(); iROF++) { + LOGP(debug, "LOOP on: ROFRecord array"); + unsigned int rofIndex = 0; + unsigned int rofNEntries = 0; + for (unsigned int iROF = 0; iROF < ROFRecordArrrayRef.size(); iROF++) { - rofIndex = ROFRecordArrrayRef[iROF].getFirstEntry(); - rofNEntries = ROFRecordArrrayRef[iROF].getNEntries(); + rofIndex = ROFRecordArrrayRef[iROF].getFirstEntry(); + rofNEntries = ROFRecordArrrayRef[iROF].getNEntries(); - // >> read and map MC events contributing to this ROF - for (int im = mcEvMin[iROF]; im <= mcEvMax[iROF]; im++) { + // >> read and map MC events contributing to this ROF + for (int im = mcEvMin[iROF]; im <= mcEvMax[iROF]; im++) { - if (hitArray[im] == nullptr) { + if (hitArray[im] == nullptr) { - hitTree->SetBranchAddress("IT3Hit", &hitArray[im]); - hitTree->GetEntry(im); + hitTree->SetBranchAddress("IT3Hit", &hitArray[im]); + hitTree->GetEntry(im); - auto& mc2hit = mc2hitVec[im]; + auto& mc2hit = mc2hitVec[im]; - for (size_t 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 (size_t 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); + } } } - } - - LOGP(debug, " `-> LOOP on: Digits array(size={}) starting at ROFIndex={} to {}", digArr->size(), rofIndex, rofIndex + rofNEntries); - for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { - int ix = (*digArr)[iDigit].getRow(), iz = (*digArr)[iDigit].getColumn(); - auto chipID = (*digArr)[iDigit].getChipIndex(); - auto layer = its3::constants::detID::getDetID2Layer(chipID); - bool isIB{its3::constants::detID::isDetITS3(chipID)}; - float x{0.f}, y{0.f}, z{0.f}; - (isIB) ? ++nDigitReadIB : ++nDigitReadOB; - - if (isIB) { - // ITS3 IB - float xFlat{0.f}, yFlat{0.f}; - mMosaixSegmentations[layer].detectorToLocal(ix, iz, xFlat, z); - mMosaixSegmentations[layer].flatToCurved(xFlat, 0., x, y); - } else { - // ITS2 OB - SegmentationAlpide::detectorToLocal(ix, iz, x, z); - } - o2::math_utils::Point3D locD(x, y, z); - auto lab = (labels.getLabels(iDigit))[0]; - int trID = lab.getTrackID(); - if (!lab.isValid()) { // not noise - continue; - } - - // get MC info - uint64_t key = (uint64_t(trID) << 32) + chipID; - const auto* mc2hit = &mc2hitVec[lab.getEventID()]; - const auto& hitEntry = mc2hit->find(key); - if (hitEntry == mc2hit->end()) { - LOGP(debug, "Failed to find MC hit entry for Tr {} chipID {}", trID, chipID); - continue; - } + LOGP(debug, " `-> LOOP on: Digits array(size={}) starting at ROFIndex={} to {}", digArr->size(), rofIndex, rofIndex + rofNEntries); + for (unsigned int iDigit = rofIndex; iDigit < rofIndex + rofNEntries; iDigit++) { + int ix = (*digArr)[iDigit].getRow(), iz = (*digArr)[iDigit].getColumn(); + auto chipID = (*digArr)[iDigit].getChipIndex(); + auto layer = its3::constants::detID::getDetID2Layer(chipID); + bool isIB{its3::constants::detID::isDetITS3(chipID)}; + float x{0.f}, y{0.f}, z{0.f}; + (isIB) ? ++nDigitReadIB : ++nDigitReadOB; + + if (isIB) { + // ITS3 IB + float xFlat{0.f}, yFlat{0.f}; + mMosaixSegmentations[layer].detectorToLocal(ix, iz, xFlat, z); + mMosaixSegmentations[layer].flatToCurved(xFlat, 0., x, y); + } else { + // ITS2 OB + SegmentationAlpide::detectorToLocal(ix, iz, x, z); + } - auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global - - ////// 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 xyzLocM; - xyzLocM.SetCoordinates(0.5f * (xyzLocE.X() + xyzLocS.X()), 0.5f * (xyzLocE.Y() + xyzLocS.Y()), 0.5f * (xyzLocE.Z() + xyzLocS.Z())); - float xlc = 0., zlc = 0.; - int row = 0, col = 0; - - if (isIB) { - float xFlat{0.}, yFlat{0.}; - mMosaixSegmentations[layer].curvedToFlat(xyzLocM.X(), xyzLocM.Y(), xFlat, yFlat); - xyzLocM.SetCoordinates(xFlat, yFlat, xyzLocM.Z()); - mMosaixSegmentations[layer].curvedToFlat(locD.X(), locD.Y(), xFlat, yFlat); - locD.SetCoordinates(xFlat, yFlat, locD.Z()); - if (auto v1 = !mMosaixSegmentations[layer].localToDetector(xyzLocM.X(), xyzLocM.Z(), row, col), - v2 = !mMosaixSegmentations[layer].detectorToLocal(row, col, xlc, zlc); - v1 || v2) { + o2::math_utils::Point3D locD(x, y, z); + auto lab = (labels.getLabels(iDigit))[0]; + int trID = lab.getTrackID(); + if (!lab.isValid()) { // not noise continue; } - } else { - if (auto v1 = !SegmentationAlpide::localToDetector(xyzLocM.X(), xyzLocM.Z(), row, col), - v2 = !SegmentationAlpide::detectorToLocal(row, col, xlc, zlc); - v1 || v2) { + + // get MC info + uint64_t key = (uint64_t(trID) << 32) + chipID; + const auto* mc2hit = &mc2hitVec[lab.getEventID()]; + const auto& hitEntry = mc2hit->find(key); + if (hitEntry == mc2hit->end()) { + LOGP(debug, "Failed to find MC hit entry for Tr {} chipID {}", trID, chipID); continue; } - } - nt->Fill(chipID, gloD.X(), gloD.Y(), gloD.Z(), ix, iz, row, col, xyzLocM.X(), xyzLocM.Z(), xlc, zlc, xyzLocM.X() - locD.X(), xyzLocM.Z() - locD.Z()); + auto gloD = gman->getMatrixL2G(chipID)(locD); // convert to global + + ////// HITS + Hit& hit = (*hitArray[lab.getEventID()])[hitEntry->second]; + // mean local position of the hit + auto locH = gman->getMatrixL2G(chipID) ^ (hit.GetPos()); // inverse conversion from global to local + auto locHsta = gman->getMatrixL2G(chipID) ^ (hit.GetPosStart()); + o2::math_utils::Point3D locHmid; + float x0, y0, z0, dltx, dlty, dltz, r; + if (isIB) { + float xFlat{0.}, yFlat{0.}; + mMosaixSegmentations[layer].curvedToFlat(locH.X(), locH.Y(), xFlat, yFlat); + locH.SetCoordinates(xFlat, yFlat, locH.Z()); + mMosaixSegmentations[layer].curvedToFlat(locHsta.X(), locHsta.Y(), xFlat, yFlat); + locHsta.SetCoordinates(xFlat, yFlat, locHsta.Z()); + x0 = locHsta.X(); + dltx = locH.X() - x0; + y0 = locHsta.Y(); + dlty = locH.Y() - y0; + z0 = locHsta.Z(); + dltz = locH.Z() - z0; + r = (o2::its3::constants::pixelarray::pixels::apts::responseYShift - y0) / dlty; + } else { + x0 = locHsta.X(); + dltx = locH.X() - x0; + y0 = locHsta.Y(); + dlty = locH.Y() - y0; + z0 = locHsta.Z(); + dltz = locH.Z() - z0; + r = (0.5 * (o2::itsmft::SegmentationAlpide::SensorLayerThickness - o2::itsmft::SegmentationAlpide::SensorLayerThicknessEff) - y0) / dlty; + } + locHmid.SetXYZ(x0 + r * dltx, y0 + r * dlty, z0 + r * dltz); + auto gloHmid = gman->getMatrixL2G(chipID) * locHmid; + float theta = std::acos(gloHmid.Z() / gloHmid.Rho()); + float eta = -std::log(std::tan(theta / 2.f)); + + float xlc = 0., zlc = 0.; + int row = 0, col = 0; + + if (isIB) { + float xFlat{0.}, yFlat{0.}; + mMosaixSegmentations[layer].curvedToFlat(locD.X(), locD.Y(), xFlat, yFlat); + locD.SetCoordinates(xFlat, yFlat, locD.Z()); + if (auto v1 = !mMosaixSegmentations[layer].localToDetector(locHmid.X(), locHmid.Z(), row, col), + v2 = !mMosaixSegmentations[layer].detectorToLocal(row, col, xlc, zlc); + v1 || v2) { + continue; + } + } else { + if (auto v1 = !SegmentationAlpide::localToDetector(locHmid.X(), locHmid.Z(), row, col), + v2 = !SegmentationAlpide::detectorToLocal(row, col, xlc, zlc); + v1 || v2) { + continue; + } + } + + nt->Fill(chipID, gloD.X(), gloD.Y(), gloD.Z(), ix, iz, row, col, locHmid.X(), locHmid.Z(), xlc, zlc, locHmid.X() - locD.X(), locHmid.Z() - locD.Z(), eta); - (isIB) ? ++nDigitFilledIB : ++nDigitFilledOB; - } // end loop on digits array - } // end loop on ROFRecords array + (isIB) ? ++nDigitFilledIB : ++nDigitFilledOB; + } // end loop on digits array + } // end loop on ROFRecords array + + Info("EXIT", "read %d filled %d in IB\n", nDigitReadIB, nDigitFilledIB); + Info("EXIT", "read %d filled %d in OB\n", nDigitReadOB, nDigitFilledOB); + } else { + f = TFile::Open("CheckDigits.root", "Open"); + nt = f->Get("ntd"); + } auto canvXY = new TCanvas("canvXY", "", 1600, 1600); canvXY->Divide(2, 2); @@ -234,7 +269,7 @@ void CheckDigitsITS3(std::string digifile = "it3digits.root", std::string hitfil nt->Draw("y:x>>h_y_vs_x_OB(1000, -50, 50, 1000, -50, 50)", "id >= 3456", "colz"); canvXY->cd(4); nt->Draw("y:z>>h_y_vs_z_OB(1000, -100, 100, 1000, -50, 50)", "id >= 3456", "colz"); - canvXY->SaveAs("it3digits_y_vs_x_vs_z.pdf"); + canvXY->SaveAs("it3digits_y_vs_x_vs_z.png"); auto canvdXdZ = new TCanvas("canvdXdZ", "", 1600, 800); canvdXdZ->Divide(2, 2); @@ -258,10 +293,8 @@ void CheckDigitsITS3(std::string digifile = "it3digits.root", std::string hitfil h = (TH2F*)gPad->GetPrimitive("h_dx_vs_dz_OB_z"); Info("OB |z|<2", "RMS(dx)=%.1f mu", h->GetRMS(2) * 1e4); Info("OB |z|<2", "RMS(dz)=%.1f mu", h->GetRMS(1) * 1e4); - canvdXdZ->SaveAs("it3digits_dx_vs_dz.pdf"); + canvdXdZ->SaveAs("it3digits_dx_vs_dz.png"); f->Write(); f->Close(); - Info("EXIT", "read %d filled %d in IB\n", nDigitReadIB, nDigitFilledIB); - Info("EXIT", "read %d filled %d in OB\n", nDigitReadOB, nDigitFilledOB); } diff --git a/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C b/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C index bfa2f3bede70d..4af1aa64a8ab2 100644 --- a/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C +++ b/Detectors/Upgrades/ITS3/macros/test/buildMatBudLUT.C @@ -18,20 +18,17 @@ #include "DetectorsBase/GeometryManager.h" #include "ITSMFTReconstruction/ChipMappingITS.h" #include "ITS3Simulation/DescriptorInnerBarrelITS3.h" +#include "ITS3Base/SpecsV2.h" #include "CommonUtils/NameConf.h" #include #include #include #endif -#ifndef GPUCA_ALIGPUCODE // this part is invisible on GPU version - o2::base::MatLayerCylSet mbLUT; bool testMBLUT(const std::string& lutFile = "matbud.root"); -bool buildMatBudLUT(int nTst = 30, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomName = ""); - struct LrData { float rMin = 0.f; float rMax = 0.f; @@ -45,14 +42,17 @@ struct LrData { std::vector lrData; void configLayers(); -bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std::string& geomNameInput) +bool buildMatBudLUT(int nTst = 30, int maxLr = -1, const std::string& outFile = "matbud.root", const std::string& geomNamePrefix = "o2sim", const std::string& opts = "") { - auto geomName = o2::base::NameConf::getGeomFileName(geomNameInput); + auto geomName = o2::base::NameConf::getGeomFileName(geomNamePrefix); if (gSystem->AccessPathName(geomName.c_str())) { // if needed, create geometry - std::cout << geomName << " does not exist. Will create it\n"; - gSystem->Exec("$O2_ROOT/bin/o2-sim -n 0"); + std::cout << geomName << " does not exist. Will create it on the fly\n"; + std::stringstream str; + // constructing an **unaligned** geom (Geant3 used since faster initialization) --> can be avoided by passing an existing geometry + str << "${O2_ROOT}/bin/o2-sim-serial -n 0 -e TGeant3 --detectorList ALICE2.1 --configKeyValues \"" << opts << "\" --field 0 -o " << geomNamePrefix; + gSystem->Exec(str.str().c_str()); } - o2::base::GeometryManager::loadGeometry(geomNameInput); + o2::base::GeometryManager::loadGeometry(geomNamePrefix); configLayers(); if (maxLr < 1) { @@ -62,7 +62,7 @@ bool buildMatBudLUT(int nTst, int maxLr, const std::string& outFile, const std:: } for (int i = 0; i < maxLr; i++) { auto& l = lrData[i]; - printf("L:%3d %6.2f247 rphiBin = 2.; zBin = 3.; - lrData.emplace_back(LrData(lrData.back().rMax, 258., zSpanH, zBin, rphiBin)); + lrData.emplace_back(lrData.back().rMax, 258., zSpanH, zBin, rphiBin); zSpanH = 247.f; // ignore large lumps of material at |z|>247 rphiBin = 2.; zBin = 999.; // no segmentation in Z - lrData.emplace_back(LrData(lrData.back().rMax, 280., zSpanH, zBin, rphiBin)); + lrData.emplace_back(lrData.back().rMax, 280., zSpanH, zBin, rphiBin); // TRD @@ -376,7 +378,7 @@ void configLayers() do { auto rmean = lrData.back().rMax + drStep / 2; rphiBin = rmean * TMath::Pi() * 2 / (NSect * 12); - lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); + lrData.emplace_back(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin); } while (lrData.back().rMax < 370); // TOF @@ -387,7 +389,7 @@ void configLayers() do { auto rmean = lrData.back().rMax + drStep / 2; rphiBin = rmean * TMath::Pi() * 2 / (NSect * 12); - lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); + lrData.emplace_back(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin); } while (lrData.back().rMax < 400); // rest @@ -398,8 +400,6 @@ void configLayers() zSpanH = lrData.back().rMax; auto rmean = lrData.back().rMax + drStep / 2; rphiBin = rmean * TMath::Pi() * 2 / (NSect * 12); - lrData.emplace_back(LrData(lrData.back().rMax, lrData.back().rMax + drStep, zSpanH, zBin, rphiBin)); + 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/FastMultEst.h b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/FastMultEst.h deleted file mode 100644 index e9da619c0efbf..0000000000000 --- a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/FastMultEst.h +++ /dev/null @@ -1,66 +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 ALICEO2_ITS3_FASTMULTEST_ -#define ALICEO2_ITS3_FASTMULTEST_ - -#include "ITSMFTReconstruction/ChipMappingITS.h" -#include "DataFormatsITSMFT/ROFRecord.h" -#include "DataFormatsITS3/CompCluster.h" -#include -#include "ITS3Reconstruction/FastMultEstConfig.h" -#include -#include - -namespace o2 -{ -namespace its3 -{ - -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 its3 -} // namespace o2 - -#endif diff --git a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/FastMultEstConfig.h b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/FastMultEstConfig.h deleted file mode 100644 index 1857176b19f1f..0000000000000 --- a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/FastMultEstConfig.h +++ /dev/null @@ -1,57 +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 FastMultEstConfig.h -/// \brief Configuration parameters for ITS fast multiplicity estimator -/// \author ruben.shahoyan@cern.ch - -#ifndef ALICEO2_ITS_FASTMULTESTCONF_H_ -#define ALICEO2_ITS_FASTMULTESTCONF_H_ - -#include "CommonUtils/ConfigurableParam.h" -#include "CommonUtils/ConfigurableParamHelper.h" -#include "ITSMFTReconstruction/ChipMappingITS.h" - -namespace o2 -{ -namespace its -{ -struct FastMultEstConfig : public o2::conf::ConfigurableParamHelper { - static constexpr int NLayers = 7; // FIXME - - /// acceptance correction per layer (relative to 1st one) - float accCorr[NLayers] = {1.f, 0.895, 0.825, 0.803, 0.720, 0.962, 0.911}; - int firstLayer = 3; /// 1st layer to account - int lastLayer = 6; /// last layer to account - float imposeNoisePerChip = 1.e-7 * 1024 * 512; // assumed noise, free parameter if<0 - - // cuts to reject to low or too high mult events - 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 { return cutMultClusLow >= 0.f && cutMultClusHigh > 0.f; }; - bool isVtxMultCutRequested() const { return cutMultVtxLow >= 0.f && cutMultVtxHigh > 0.f; }; - 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); } - - O2ParamDef(FastMultEstConfig, "fastMultConfig"); -}; - -} // namespace its -} // namespace o2 - -#endif diff --git a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/IOUtils.h index 771b13539b759..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 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 ab2ff0086200b..3b743c59524d2 100644 --- a/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h +++ b/Detectors/Upgrades/ITS3/reconstruction/include/ITS3Reconstruction/TrackingInterface.h @@ -28,9 +28,10 @@ class ITS3TrackingInterface final : public its::ITSTrackingInterface void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) final; protected: - void loadROF(gsl::span& trackROFspan, + 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/FastMultEst.cxx b/Detectors/Upgrades/ITS3/reconstruction/src/FastMultEst.cxx deleted file mode 100644 index fa2ce319328b5..0000000000000 --- a/Detectors/Upgrades/ITS3/reconstruction/src/FastMultEst.cxx +++ /dev/null @@ -1,207 +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 "ITS3Reconstruction/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 - // - // minimize the form sum_lr (noise_i - mu nchips_i)^2 / (mu nchips_i) + lambda_i * (noise_i + mult*acc_i - ncl_i) - // whith noise_i being estimate of the noise clusters in nchips_i of layer i, mu is the mean noise per chip, - // mult is the number of signal clusters on the ref. (1st) layer and the acc_i is the acceptance of layer i wrt 1st. - // The lambda_i is hust a Lagrange multiplier. - - const auto& conf = FastMultEstConfig::Instance(); - float w2sum = 0., wnsum = 0., wsum = 0.; - nLayersUsed = 0; - for (int il = conf.firstLayer; il <= conf.lastLayer; il++) { - if (ncl[il] > 0) { - float nchInv = 1. / o2::itsmft::ChipMappingITS::getNChipsPerLr(il); - w2sum += conf.accCorr[il] * conf.accCorr[il] * nchInv; - wnsum += ncl[il] * nchInv * conf.accCorr[il]; - wsum += conf.accCorr[il]; - nLayersUsed++; - } - } - mult = 0; - chi2 = -1; - noisePerChip = conf.imposeNoisePerChip; - if (nLayersUsed < 1) { - return -1; - } - auto w2sumI = 1. / w2sum; - mult = (wnsum - noisePerChip * wsum) * w2sumI; - cov[0] = wnsum * w2sumI; - cov[2] = 0.; - cov[1] = 0.; - - chi2 = 0.; - for (int il = conf.firstLayer; il <= conf.lastLayer; il++) { - if (ncl[il] > 0) { - float noise = ncl[il] - mult * conf.accCorr[il], estNoise = o2::itsmft::ChipMappingITS::getNChipsPerLr(il) * noisePerChip; - float diff = noise - estNoise; - chi2 += diff * diff / estNoise; - } - } - chi2 = nLayersUsed > 2 ? chi2 / (nLayersUsed - 2) : 0.; - return mult > 0 ? mult : 0; -} - -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/Upgrades/ITS3/reconstruction/src/IOUtils.cxx b/Detectors/Upgrades/ITS3/reconstruction/src/IOUtils.cxx index acba8022e376f..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 { @@ -33,6 +28,8 @@ void convertCompactClusters(gsl::span clusters, const its3::TopologyDictionary* dict) { auto geom = o2::its::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); + bool applyMisalignment = false; const auto& conf = o2::its::TrackerParamConfig::Instance(); for (int il = 0; il < geom->getNumberOfLayers(); ++il) { @@ -43,64 +40,70 @@ 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(); - auto& cl3d = output.emplace_back(detID, - (its3::constants::detID::isDetITS3(detID) ? geom->getT2LMatrixITS3(detID, geom->getSensorRefAlpha(detID)) : geom->getMatrixT2L(detID)) ^ locXYZ); // local --> tracking + // 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); } } int loadROFrameDataITS3(its::TimeFrame<7>* tf, - gsl::span rofs, + gsl::span rofs, gsl::span clusters, gsl::span::iterator& pattIt, const its3::TopologyDictionary* dict, const dataformats::MCTruthContainer* mcLabels) { - tf->resetROFrameData(); - auto geom = its::GeometryTGeo::Instance(); geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::T2L, o2::math_utils::TransformType::L2G)); - tf->mNrof = 0; + // tf->resetROFrameData(rofs.size()); // FIXME + // tf->prepareROFrameData(rofs, clusters); FIXME its::bounded_vector clusterSizeVec(clusters.size(), tf->getMemoryPool().get()); - for (auto& rof : rofs) { + 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 isITS3 = its3::constants::detID::isDetITS3(sensorID); - 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 + const o2::math_utils::Point3D trkXYZ = geom->getMatrixT2L(sensorID) ^ locXYZ; - // for cylindrical layers we have a different alpha for each cluster, for regular silicon detectors instead a single alpha for the whole sensor + // 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); - o2::math_utils::Point3D trkXYZ; - if (isITS3) { - // Inverse transformation to the local --> tracking - trkXYZ = geom->getT2LMatrixITS3(sensorID, alpha) ^ locXYZ; - } else { - // Inverse transformation to the local --> tracking - trkXYZ = geom->getMatrixT2L(sensorID) ^ locXYZ; + 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(), trkXYZ.x(), alpha, - std::array{trkXYZ.y(), trkXYZ.z()}, + 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 @@ -108,12 +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].push_back(tf->getUnsortedClusters()[iL].size()); + tf->mROFramesClusters[iL][iRof + 1] = (int)tf->getUnsortedClusters()[iL].size(); } - tf->mNrof++; } - tf->setClusterSize(clusterSizeVec); + // tf->setClusterSize(clusterSizeVec); FIXME for (auto& v : tf->mNTrackletsPerCluster) { v.resize(tf->getUnsortedClusters()[1].size()); @@ -123,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 3d18ab267bd46..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" @@ -74,12 +74,13 @@ void ITS3TrackingInterface::finaliseCCDB(framework::ConcreteDataMatcher& matcher } } -void ITS3TrackingInterface::loadROF(gsl::span& trackROFspan, +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/simulation/include/ITS3Simulation/DescriptorInnerBarrelITS3.h b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DescriptorInnerBarrelITS3.h index d1b54f81face4..3e230cee474bd 100644 --- a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DescriptorInnerBarrelITS3.h +++ b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DescriptorInnerBarrelITS3.h @@ -40,16 +40,19 @@ class DescriptorInnerBarrelITS3 : public o2::its::DescriptorInnerBarrel void createLayer(int idLayer, TGeoVolume* dest); void createServices(TGeoVolume* dest); void configure() {} + void addAlignableVolumesLayer(int idLayer, int wrapperLayerId, TString& parentPath, int& lastUID) const; protected: - int mNumLayers{constants::nLayers}; - // wrapper volume properties - static constexpr double mWrapperMinRadiusITS3{1.8}; - static constexpr double mWrapperMaxRadiusITS3{4.}; - static constexpr double mWrapperZSpanITS3{constants::segment::length + 5.}; + static constexpr double mTolerance{1e-3}; + static constexpr double mWrapperMinRadiusITS3{constants::radiiInner[0] - mTolerance}; + static constexpr double mWrapperMaxRadiusITS3{constants::services::radiusOuter + mTolerance}; + static constexpr double mWrapperZSpanITS3{(constants::services::length * 2) + mTolerance}; // z length is divided in half private: + void addAlignableVolumesHalfBarrel(int idLayer, int iHB, TString& parentPath, int& lastUID) const; + void addAlignableVolumesChips(int idLayer, int iHalfBarrel, TString& parentPath, int& lastUID) const; + std::array, constants::nLayers> mIBLayers; std::unique_ptr mServices; diff --git a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DigiParams.h b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DigiParams.h index 5764dfbd7d593..e3a2a5d0d0efb 100644 --- a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DigiParams.h +++ b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/DigiParams.h @@ -48,16 +48,17 @@ class DigiParams final : public o2::itsmft::DigiParams const o2::itsmft::AlpideSimResponse* getOBSimResponse() const { return mOBSimResponse; } void setOBSimResponse(const o2::itsmft::AlpideSimResponse* response) { mOBSimResponse = response; } - o2::its3::ChipSimResponse* getIBSimResponse() const { return mIBSimResponse; } - void setIBSimResponse(o2::its3::ChipSimResponse* response); + o2::its3::ChipSimResponse* getIBSimResponse() const { return mIBSimResponse.get(); } + void setIBSimResponse(const o2::itsmft::AlpideSimResponse* resp); bool hasResponseFunctions() const { return mIBSimResponse != nullptr && mOBSimResponse != nullptr; } void print() const final; private: - const o2::itsmft::AlpideSimResponse* mOBSimResponse = nullptr; //!< pointer to external response - o2::its3::ChipSimResponse* mIBSimResponse = nullptr; //!< pointer to external response + const o2::itsmft::AlpideSimResponse* mOBSimResponse = nullptr; //!< pointer to external response + const o2::itsmft::AlpideSimResponse* mIBSimResponseExt = nullptr; //!< pointer to external response + std::unique_ptr mIBSimResponse = nullptr; //!< pointer to external response ClassDef(DigiParams, 1); }; diff --git a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h index a2dd1102091da..866973083983b 100644 --- a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h +++ b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/Digitizer.h @@ -42,7 +42,7 @@ class Digitizer : public TObject using ExtraDig = std::vector; ///< container for extra contributions to PreDigits public: - ~Digitizer(); + ~Digitizer() = default; void setDigits(std::vector* dig) { mDigits = dig; } void setMCLabels(o2::dataformats::MCTruthContainer* mclb) { mMCLabels = mclb; } @@ -111,18 +111,18 @@ class Digitizer : public TObject static constexpr std::array mIBSegmentations{0, 1, 2}; - o2::its3::ChipSimResponse* mSimRespIB = nullptr; // simulated response for IB - o2::itsmft::AlpideSimResponse* mSimRespOB = nullptr; // simulated response for OB - bool mSimRespIBOrientation{false}; // wether the orientation in the IB response function is flipped - float mSimRespIBShift{0.f}; // adjusting the Y-shift in the IB response function to match sensor local coord. - float mSimRespIBScaleX{1.f}; // scale x-local coordinate to response function x-coordinate - float mSimRespIBScaleZ{1.f}; // scale z-local coordinate to response function z-coordinate - float mSimRespOBShift{0.f}; // adjusting the Y-shift in the OB response function to match sensor local coord. + const o2::its3::ChipSimResponse* mSimRespIB = nullptr; // simulated response for IB + const o2::itsmft::AlpideSimResponse* mSimRespOB = nullptr; // simulated response for OB + bool mSimRespIBOrientation{false}; // wether the orientation in the IB response function is flipped + float mSimRespIBShift{0.f}; // adjusting the Y-shift in the IB response function to match sensor local coord. + float mSimRespIBScaleX{1.f}; // scale x-local coordinate to response function x-coordinate + float mSimRespIBScaleZ{1.f}; // scale z-local coordinate to response function z-coordinate + float mSimRespOBShift{0.f}; // adjusting the Y-shift in the OB response function to match sensor local coord. const o2::its::GeometryTGeo* mGeometry = nullptr; ///< ITS3 geometry - std::vector mChips; ///< Array of chips digits containers - std::deque> mExtraBuff; ///< burrer (per roFrame) for extra digits + std::vector mChips; ///< Array of chips digits containers + std::deque> mExtraBuff; ///< burrer (per roFrame) for extra digits std::vector* mDigits = nullptr; //! output digits std::vector* mROFRecords = nullptr; //! output ROF records diff --git a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/ITS3Layer.h b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/ITS3Layer.h index fd9195f9ee228..f45a4469ae2b8 100644 --- a/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/ITS3Layer.h +++ b/Detectors/Upgrades/ITS3/simulation/include/ITS3Simulation/ITS3Layer.h @@ -26,7 +26,7 @@ namespace o2::its3 { /// This class defines the geometry for the ITS3 IB layers. -class ITS3Layer +class ITS3Layer final { // The hierarchy will be the following: // ITS2 -> ITS3 @@ -76,7 +76,6 @@ class ITS3Layer void buildPartial(TGeoVolume* motherVolume, TGeoMatrix* mat = nullptr, BuildLevel level = BuildLevel::kAll, bool createMaterials = false); private: - bool mBuilt{false}; TGeoMedium* mSilicon{nullptr}; TGeoMedium* mAir{nullptr}; TGeoMedium* mCarbon{nullptr}; @@ -91,7 +90,7 @@ class ITS3Layer void createSegment(); void createChip(); void createCarbonForm(); - TGeoCompositeShape* getHringShape(TGeoTubeSeg* Hring); + TGeoCompositeShape* getHringShape(TGeoTubeSeg* Hring) const; void createLayerImpl(); uint8_t mNLayer{0}; // Layer number diff --git a/Detectors/Upgrades/ITS3/simulation/src/DescriptorInnerBarrelITS3.cxx b/Detectors/Upgrades/ITS3/simulation/src/DescriptorInnerBarrelITS3.cxx index 540e1d41f1c62..42644fbfe0c38 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/DescriptorInnerBarrelITS3.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/DescriptorInnerBarrelITS3.cxx @@ -10,7 +10,8 @@ // or submit itself to any jurisdiction. #include "ITS3Simulation/DescriptorInnerBarrelITS3.h" -#include "fairlogger/Logger.h" +#include "ITSBase/GeometryTGeo.h" +#include "Framework/Logger.h" using namespace o2::its3; @@ -18,14 +19,54 @@ ClassImp(DescriptorInnerBarrelITS3); void DescriptorInnerBarrelITS3::createLayer(int iLayer, TGeoVolume* dest) { - LOGP(debug, "ITS3-IB: Creating Layer {}", iLayer); mIBLayers[iLayer] = std::make_unique(iLayer); mIBLayers[iLayer]->createLayer(dest); } void DescriptorInnerBarrelITS3::createServices(TGeoVolume* dest) { - LOGP(debug, "ITS3-IB: Creating Services"); mServices = std::make_unique(); mServices->createCYSSAssembly(dest); } + +void DescriptorInnerBarrelITS3::addAlignableVolumesLayer(int idLayer, int wrapperLayerId, TString& parentPath, int& lastUID) const +{ + TString wrpV = wrapperLayerId != -1 ? Form("%s%d_1", its::GeometryTGeo::getITSWrapVolPattern(), wrapperLayerId) : ""; + TString path = Form("%s/%s/%s%d_0", parentPath.Data(), wrpV.Data(), its::GeometryTGeo::getITS3LayerPattern(), idLayer); + TString sname = its::GeometryTGeo::composeSymNameLayer(idLayer, true); + + for (int iHalfBarrel{0}; iHalfBarrel < 2; ++iHalfBarrel) { + addAlignableVolumesHalfBarrel(idLayer, iHalfBarrel, path, lastUID); + } +} + +void DescriptorInnerBarrelITS3::addAlignableVolumesHalfBarrel(int idLayer, int iHB, TString& parentPath, int& lastUID) const +{ + // for ITS3 smallest alignable volume is the half-barrel (e.g., the carbon-form composite structure with the sensors) + TString path = Form("%s/%s%d_%d", parentPath.Data(), its::GeometryTGeo::getITS3HalfBarrelPattern(), idLayer, iHB); + TString sname = its::GeometryTGeo::composeSymNameHalfBarrel(idLayer, iHB, true); + if (!gGeoManager->SetAlignableEntry(sname.Data(), path.Data())) { + LOG(fatal) << "Unable to set alignable entry ! " << sname << " : " << path; + } + addAlignableVolumesChips(idLayer, iHB, path, lastUID); +} + +void DescriptorInnerBarrelITS3::addAlignableVolumesChips(int idLayer, int iHB, TString& parentPath, int& lastUID) const +{ + for (int seg{0}; seg < constants::nSegments[idLayer]; ++seg) { + for (int rsu{0}; rsu < constants::segment::nRSUs; ++rsu) { + for (int tile{0}; tile < constants::rsu::nTiles; ++tile) { + TString path = parentPath; + path += Form("/%s_0/", its::GeometryTGeo::getITS3ChipPattern(idLayer)); + path += Form("%s_%d/", its::GeometryTGeo::getITS3SegmentPattern(idLayer), seg); + path += Form("%s_%d/", its::GeometryTGeo::getITS3RSUPattern(idLayer), rsu); + path += Form("%s_%d/", its::GeometryTGeo::getITS3TilePattern(idLayer), tile); + TString sname = its::GeometryTGeo::composeSymNameChip(idLayer, iHB, 0, seg, rsu, tile, true); + if (!gGeoManager->SetAlignableEntry(sname.Data(), path.Data())) { + LOG(fatal) << "Unable to set alignable entry ! " << sname << " : " << path; + } + ++lastUID; + } + } + } +} diff --git a/Detectors/Upgrades/ITS3/simulation/src/DigiParams.cxx b/Detectors/Upgrades/ITS3/simulation/src/DigiParams.cxx index afa02ec44741d..e5923d0bb7a1e 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/DigiParams.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/DigiParams.cxx @@ -69,12 +69,14 @@ void DigiParams::print() const getSignalShape().print(); } -void DigiParams::setIBSimResponse(o2::its3::ChipSimResponse* response) +void DigiParams::setIBSimResponse(const o2::itsmft::AlpideSimResponse* resp) { - mIBSimResponse = response; - if (mIBSimResponse) { - mIBSimResponse->computeCentreFromData(); + if (!resp) { + LOGP(fatal, "cannot set response from nullptr"); } + mIBSimResponseExt = resp; + mIBSimResponse = std::make_unique(mIBSimResponseExt); + mIBSimResponse->computeCentreFromData(); } } // namespace o2::its3 diff --git a/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx b/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx index b91e17890a6d8..4b0374d925401 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/Digitizer.cxx @@ -35,11 +35,6 @@ using o2::itsmft::PreDigit; using namespace o2::its3; -Digitizer::~Digitizer() -{ - delete mSimRespIB; -} - void Digitizer::init() { const int numOfChips = mGeometry->getNumberOfChips(); @@ -53,46 +48,22 @@ void Digitizer::init() } if (!mParams.hasResponseFunctions()) { - auto loadSetResponseFunc = [&](const char* fileIB, const char* nameIB, const char* fileOB, const char* nameOB) { - LOGP(info, "Loading response function IB={}:{} ; OB={}:{}", nameIB, fileIB, nameOB, fileOB); - auto fIB = TFile::Open(fileIB, "READ"); - if (!fIB || fIB->IsZombie() || !fIB->IsOpen()) { - LOGP(fatal, "Cannot open file {}", fileIB); - } - auto fOB = TFile::Open(fileOB, "READ"); - if (!fOB || fOB->IsZombie() || !fOB->IsOpen()) { - LOGP(fatal, "Cannot open file {}", fileOB); - } - if ((mSimRespIB = new o2::its3::ChipSimResponse(fIB->Get(nameIB))) == nullptr) { - LOGP(fatal, "Cannot create response function for IB"); - } - if ((mSimRespOB = fOB->Get(nameOB)) == nullptr) { - LOGP(fatal, "Cannot create response function for OB"); - } - mParams.setIBSimResponse(mSimRespIB); - mParams.setOBSimResponse(mSimRespOB); - fIB->Close(); - fOB->Close(); - }; - - if (const auto& func = ITS3Params::Instance().chipResponseFunction; func == "Alpide") { - constexpr const char* responseFile = "$(O2_ROOT)/share/Detectors/ITSMFT/data/AlpideResponseData/AlpideResponseData.root"; - loadSetResponseFunc(responseFile, "response0", responseFile, "response0"); - mSimRespIBScaleX = o2::itsmft::SegmentationAlpide::PitchRow / SegmentationIB::PitchRow; - mSimRespIBScaleZ = o2::itsmft::SegmentationAlpide::PitchCol / SegmentationIB::PitchCol; - } else if (func == "APTS") { - constexpr const char* responseFileIB = "$(O2_ROOT)/share/Detectors/Upgrades/ITS3/data/ITS3ChipResponseData/APTSResponseData.root"; - constexpr const char* responseFileOB = "$(O2_ROOT)/share/Detectors/ITSMFT/data/AlpideResponseData/AlpideResponseData.root"; - loadSetResponseFunc(responseFileIB, "response1", responseFileOB, "response0"); - mSimRespIBScaleX = constants::pixelarray::pixels::apts::pitchX / SegmentationIB::PitchRow; - mSimRespIBScaleZ = constants::pixelarray::pixels::apts::pitchZ / SegmentationIB::PitchCol; - mSimRespIBOrientation = true; - } else { - LOGP(fatal, "ResponseFunction '{}' not implemented!", func); - } - mSimRespIBShift = mSimRespIB->getDepthMax() - constants::silicon::thickness / 2.f; - mSimRespOBShift = mSimRespOB->getDepthMax() - SegmentationOB::SensorLayerThickness / 2.f; + LOGP(fatal, "No response functions set!"); + } + if (const auto& func = ITS3Params::Instance().chipResponseFunction; func == "Alpide") { + mSimRespIBScaleX = o2::itsmft::SegmentationAlpide::PitchRow / SegmentationIB::PitchRow; + mSimRespIBScaleZ = o2::itsmft::SegmentationAlpide::PitchCol / SegmentationIB::PitchCol; + } else if (func == "APTS") { + mSimRespIBScaleX = constants::pixelarray::pixels::apts::pitchX / SegmentationIB::PitchRow; + mSimRespIBScaleZ = constants::pixelarray::pixels::apts::pitchZ / SegmentationIB::PitchCol; + mSimRespIBOrientation = true; + } else { + LOGP(fatal, "ResponseFunction '{}' not implemented!", func); } + mSimRespIB = mParams.getIBSimResponse(); + mSimRespOB = mParams.getOBSimResponse(); + mSimRespIBShift = mSimRespIB->getDepthMax() - constants::silicon::thickness / 2.f; + mSimRespOBShift = mSimRespOB->getDepthMax() - SegmentationOB::SensorLayerThickness / 2.f; mParams.print(); LOGP(info, "IB shift = {} ; OB shift = {}", mSimRespIBShift, mSimRespOBShift); @@ -105,10 +76,10 @@ void Digitizer::process(const std::vector* hits, int evID, int srcI { // digitize single event, the time must have been set beforehand - LOG(info) << "Digitizing " << mGeometry->getName() << " hits of entry " << evID << " from source " - << srcID << " at time " << mEventTime << " ROFrame = " << mNewROFrame << ")" - << " cont.mode: " << isContinuous() - << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; + LOG(debug) << "Digitizing " << mGeometry->getName() << " hits of entry " << evID << " from source " + << srcID << " at time " << mEventTime << " ROFrame = " << mNewROFrame << ")" + << " cont.mode: " << isContinuous() + << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax; // is there something to flush ? if (mNewROFrame > mROFrameMin) { @@ -234,7 +205,7 @@ void Digitizer::fillOutputContainer(uint32_t frameLast) void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID) { // convert single hit to digits - int chipID = hit.GetDetectorID(); + auto chipID = hit.GetDetectorID(); auto& chip = mChips[chipID]; if (chip.isDisabled()) { return; diff --git a/Detectors/Upgrades/ITS3/simulation/src/ITS3Layer.cxx b/Detectors/Upgrades/ITS3/simulation/src/ITS3Layer.cxx index 8dc94e339c793..c0f8fdc19d03b 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/ITS3Layer.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/ITS3Layer.cxx @@ -67,11 +67,11 @@ void ITS3Layer::createLayer(TGeoVolume* motherVolume) // Create one layer of ITS3 and attach it to the motherVolume. getMaterials(); createLayerImpl(); - mBuilt = true; if (motherVolume == nullptr) { return; } + // Add it to motherVolume auto* trans = new TGeoTranslation(0, 0, -constants::segment::lengthSensitive / 2.); motherVolume->AddNode(mLayer, 0, trans); @@ -122,8 +122,8 @@ void ITS3Layer::createTile() mTile->AddNode(mPixelArray, 0, phiRotPixelArray); // Biasing - double biasPhi1 = constants::pixelarray::width / mR * o2m::Rad2Deg + readoutPhi2; - double biasPhi2 = biasing::width / mR * o2m::Rad2Deg + biasPhi1; + double biasPhi1 = (constants::pixelarray::width / mR * o2m::Rad2Deg) + readoutPhi2; + double biasPhi2 = (biasing::width / mR * o2m::Rad2Deg) + biasPhi1; auto biasing = new TGeoTubeSeg(mRmin, mRmax, biasing::length / 2, biasPhi1, biasPhi2); auto biasingVol = new TGeoVolume(Form("biasing%d", mNLayer), biasing, mSilicon); biasingVol->SetLineColor(biasing::color); @@ -131,9 +131,9 @@ void ITS3Layer::createTile() mTile->AddNode(biasingVol, 0); // Power Switches are on the side right side of the pixel array and biasing. - auto zMovePowerSwitches = new TGeoTranslation(0, 0, +powerswitches::length / 2. + constants::pixelarray::length / 2.); + auto zMovePowerSwitches = new TGeoTranslation(0, 0, (+powerswitches::length / 2.) + (constants::pixelarray::length / 2.)); double powerPhi1 = readoutPhi2; - double powerPhi2 = powerswitches::width / mR * o2m::Rad2Deg + powerPhi1; + double powerPhi2 = (powerswitches::width / mR * o2m::Rad2Deg) + powerPhi1; auto powerSwitches = new TGeoTubeSeg(mRmin, mRmax, powerswitches::length / 2, powerPhi1, powerPhi2); auto powerSwitchesVol = new TGeoVolume(Form("powerswitches%d", mNLayer), powerSwitches, mSilicon); powerSwitchesVol->SetLineColor(powerswitches::color); @@ -166,7 +166,7 @@ void ITS3Layer::createRSU() // Lower Left auto zMoveLL1 = new TGeoTranslation(0, 0, constants::tile::length); auto zMoveLL2 = new TGeoTranslation(0, 0, constants::tile::length * 2.); - auto zMoveLLDB = new TGeoTranslation(0, 0, -databackbone::length / 2. - constants::pixelarray::length / 2.); + auto zMoveLLDB = new TGeoTranslation(0, 0, (-databackbone::length / 2.) - (constants::pixelarray::length / 2.)); // Lets attach the tiles to the QS. mRSU->AddNode(mTile, nCopyRSU++, nullptr); mRSU->AddNode(mTile, nCopyRSU++, zMoveLL1); @@ -175,9 +175,9 @@ void ITS3Layer::createRSU() // Lower Right auto zMoveLR0 = new TGeoTranslation(0, 0, +length / 2.); - auto zMoveLR1 = new TGeoTranslation(0, 0, constants::tile::length + length / 2.); - auto zMoveLR2 = new TGeoTranslation(0, 0, constants::tile::length * 2. + length / 2.); - auto zMoveLRDB = new TGeoTranslation(0, 0, -databackbone::length / 2. + length / 2. - constants::pixelarray::length / 2.); + auto zMoveLR1 = new TGeoTranslation(0, 0, constants::tile::length + (length / 2.)); + auto zMoveLR2 = new TGeoTranslation(0, 0, (constants::tile::length * 2.) + (length / 2.)); + auto zMoveLRDB = new TGeoTranslation(0, 0, (-databackbone::length / 2.) + (length / 2.) - (constants::pixelarray::length / 2.)); // Lets attach the tiles to the QS. mRSU->AddNode(mTile, nCopyRSU++, zMoveLR0); mRSU->AddNode(mTile, nCopyRSU++, zMoveLR1); @@ -192,7 +192,7 @@ void ITS3Layer::createRSU() // Upper Left auto zMoveUL1 = new TGeoCombiTrans(0, 0, constants::tile::length, rot); auto zMoveUL2 = new TGeoCombiTrans(0, 0, constants::tile::length * 2., rot); - auto zMoveULDB = new TGeoCombiTrans(0, 0, -databackbone::length / 2. - constants::pixelarray::length / 2., rot); + auto zMoveULDB = new TGeoCombiTrans(0, 0, (-databackbone::length / 2.) - (constants::pixelarray::length / 2.), rot); // Lets attach the tiles to the QS. mRSU->AddNode(mTile, nCopyRSU++, rot); mRSU->AddNode(mTile, nCopyRSU++, zMoveUL1); @@ -201,9 +201,9 @@ void ITS3Layer::createRSU() // Upper Right auto zMoveUR0 = new TGeoCombiTrans(0, 0, +length / 2., rot); - auto zMoveUR1 = new TGeoCombiTrans(0, 0, constants::tile::length + length / 2., rot); - auto zMoveUR2 = new TGeoCombiTrans(0, 0, constants::tile::length * 2. + length / 2., rot); - auto zMoveURDB = new TGeoCombiTrans(0, 0, -databackbone::length / 2. + length / 2. - constants::pixelarray::length / 2., rot); + auto zMoveUR1 = new TGeoCombiTrans(0, 0, constants::tile::length + (length / 2.), rot); + auto zMoveUR2 = new TGeoCombiTrans(0, 0, (constants::tile::length * 2.) + (length / 2.), rot); + auto zMoveURDB = new TGeoCombiTrans(0, 0, (-databackbone::length / 2.) + (length / 2.) - (constants::pixelarray::length / 2.), rot); // Lets attach the tiles to the QS. mRSU->AddNode(mTile, nCopyRSU++, zMoveUR0); mRSU->AddNode(mTile, nCopyRSU++, zMoveUR1); @@ -225,9 +225,9 @@ void ITS3Layer::createSegment() mSegment = new TGeoVolumeAssembly(its3TGeo::getITS3SegmentPattern(mNLayer)); mSegment->VisibleDaughters(); - for (size_t i{0}; i < nRSUs; ++i) { - auto zMove = new TGeoTranslation(0, 0, +i * constants::rsu::length + constants::rsu::databackbone::length + constants::pixelarray::length / 2.); - mSegment->AddNode(mRSU, i, zMove); + for (unsigned int i{0}; i < nRSUs; ++i) { + auto zMove = new TGeoTranslation(0, 0, (i * constants::rsu::length) + constants::rsu::databackbone::length + (constants::pixelarray::length / 2.)); + mSegment->AddNode(mRSU, (int)i, zMove); } // LEC @@ -242,7 +242,7 @@ void ITS3Layer::createSegment() mSegment->AddNode(lecVol, 0, zMoveLEC); // REC; reuses lecPhi1,2 - auto zMoveREC = new TGeoTranslation(0, 0, nRSUs * constants::rsu::length + rec::length / 2.); + auto zMoveREC = new TGeoTranslation(0, 0, (nRSUs * constants::rsu::length) + (rec::length / 2.)); auto rec = new TGeoTubeSeg(mRmin, mRmax, rec::length / 2., lecPhi1, lecPhi2); auto recVol = new TGeoVolume(Form("rec%d", mNLayer), rec, mSilicon); @@ -266,11 +266,11 @@ void ITS3Layer::createChip() auto phiOffset = constants::segment::width / mR * o2m::Rad2Deg; for (unsigned int i{0}; i < constants::nSegments[mNLayer]; ++i) { auto rot = new TGeoRotation(Form("its3PhiSegmentOffset_%d_%d", mNLayer, i), 0, 0, phiOffset * i); - mChip->AddNode(mSegment, i, rot); + mChip->AddNode(mSegment, (int)i, rot); } // Add metal stack positioned radially outward - auto zMoveMetal = new TGeoTranslation(0, 0, constants::metalstack::length / 2. - constants::segment::lec::length); + auto zMoveMetal = new TGeoTranslation(0, 0, (constants::metalstack::length / 2.) - constants::segment::lec::length); auto metal = new TGeoTubeSeg(mRmax, mRmax + constants::metalstack::thickness, constants::metalstack::length / 2., 0, constants::nSegments[mNLayer] * phiOffset); auto metalVol = new TGeoVolume(Form("metal%d", mNLayer), metal, mCopper); metalVol->SetLineColor(constants::metalstack::color); @@ -293,10 +293,10 @@ void ITS3Layer::createCarbonForm() if (mNLayer < 2) { dRadius = constants::radii[mNLayer + 1] - constants::radii[mNLayer] - constants::totalThickness; } else { - dRadius = 0.7; // TODO: lack of carbon foam radius for layer 2, use 0.7mm as a temporary value + dRadius = constants::carbonfoam::thicknessOuterFoam; // TODO: lack of carbon foam radius for layer 2, use 0.7 cm as a temporary value } double phiSta = edgeBetwChipAndFoam / (0.5 * constants::radii[mNLayer + 1] + constants::radii[mNLayer]) * o2m::Rad2Deg; - double phiEnd = (constants::nSegments[mNLayer] * constants::segment::width) / constants::radii[mNLayer] * o2m::Rad2Deg - phiSta; + double phiEnd = ((constants::nSegments[mNLayer] * constants::segment::width) / constants::radii[mNLayer] * o2m::Rad2Deg) - phiSta; double phiLongeronsCover = longeronsWidth / (0.5 * constants::radii[mNLayer + 1] + constants::radii[mNLayer]) * o2m::Rad2Deg; // H-rings foam @@ -308,35 +308,37 @@ void ITS3Layer::createCarbonForm() HringCVol->SetLineColor(color); auto HringAVol = new TGeoVolume(Form("hringA%d", mNLayer), HringAWithHoles, mCarbon); HringAVol->SetLineColor(color); - auto zMoveHringC = new TGeoTranslation(0, 0, -constants::segment::lec::length + HringLength / 2.); - auto zMoveHringA = new TGeoTranslation(0, 0, -constants::segment::lec::length + HringLength / 2. + constants::segment::length - HringLength); + auto zMoveHringC = new TGeoTranslation(0, 0, -constants::segment::lec::length + (HringLength / 2.)); + auto zMoveHringA = new TGeoTranslation(0, 0, -constants::segment::lec::length + (HringLength / 2.) + constants::segment::length - HringLength); // Longerons are made by same material - [[maybe_unused]] auto longeronR = new TGeoTubeSeg(Form("longeronR%d", mNLayer), mRmax, mRmax + dRadius, longeronsLength / 2, phiSta, phiSta + phiLongeronsCover); - [[maybe_unused]] auto longeronL = new TGeoTubeSeg(Form("longeronL%d", mNLayer), mRmax, mRmax + dRadius, longeronsLength / 2, phiEnd - phiLongeronsCover, phiEnd); - TString nameLongerons = Form("longeronR%d + longeronL%d", mNLayer, mNLayer); - auto longerons = new TGeoCompositeShape(nameLongerons); - auto longeronsVol = new TGeoVolume(Form("longerons%d", mNLayer), longerons, mCarbon); - longeronsVol->SetLineColor(color); - auto zMoveLongerons = new TGeoTranslation(0, 0, -constants::segment::lec::length + constants::segment::length / 2.); + // added separately to make navigation faster + [[maybe_unused]] auto longeronR = new TGeoTubeSeg(Form("longeronR%d", mNLayer), mRmax, mRmax + dRadius, longeronsLength / 2., phiSta, phiSta + phiLongeronsCover); + [[maybe_unused]] auto longeronL = new TGeoTubeSeg(Form("longeronL%d", mNLayer), mRmax, mRmax + dRadius, longeronsLength / 2., phiEnd - phiLongeronsCover, phiEnd); + auto longeronRVol = new TGeoVolume(Form("longeronR%d", mNLayer), longeronR, mCarbon); + longeronRVol->SetLineColor(color); + auto longeronLVol = new TGeoVolume(Form("longeronL%d", mNLayer), longeronL, mCarbon); + longeronLVol->SetLineColor(color); + auto zMoveLongerons = new TGeoTranslation(0, 0, -constants::segment::lec::length + (constants::segment::length / 2.)); mCarbonForm->AddNode(HringCVol, 0, zMoveHringC); mCarbonForm->AddNode(HringAVol, 0, zMoveHringA); - mCarbonForm->AddNode(longeronsVol, 0, zMoveLongerons); + mCarbonForm->AddNode(longeronRVol, 0, zMoveLongerons); + mCarbonForm->AddNode(longeronLVol, 0, zMoveLongerons); mCarbonForm->AddNode(mChip, 0); } -TGeoCompositeShape* ITS3Layer::getHringShape(TGeoTubeSeg* Hring) +TGeoCompositeShape* ITS3Layer::getHringShape(TGeoTubeSeg* Hring) const { // Function to dig holes in H-rings using namespace constants::carbonfoam; double stepPhiHoles = (Hring->GetPhi2() - Hring->GetPhi1()) / (nHoles[mNLayer]); - double phiHolesSta = Hring->GetPhi1() + stepPhiHoles / 2.; + double phiHolesSta = Hring->GetPhi1() + (stepPhiHoles / 2.); double radiusHring = 0.5 * (Hring->GetRmin() + Hring->GetRmax()); TGeoCompositeShape* HringWithHoles = nullptr; TString nameAllHoles = ""; for (int iHoles = 0; iHoles < nHoles[mNLayer]; iHoles++) { - double phiHole = phiHolesSta + stepPhiHoles * iHoles; + double phiHole = phiHolesSta + (stepPhiHoles * iHoles); TString nameHole = Form("hole_%d_%d", iHoles, mNLayer); [[maybe_unused]] auto hole = new TGeoTube(nameHole, 0, radiusHoles[mNLayer], 3 * Hring->GetDz()); // move hole to the hring radius @@ -376,9 +378,7 @@ void ITS3Layer::createLayerImpl() void ITS3Layer::buildPartial(TGeoVolume* motherVolume, TGeoMatrix* mat, BuildLevel level, bool createMaterials) { - if (!mBuilt) { - getMaterials(createMaterials); - } + getMaterials(createMaterials); switch (level) { case BuildLevel::kPixelArray: createPixelArray(); diff --git a/Detectors/Upgrades/ITS3/simulation/src/ITS3Services.cxx b/Detectors/Upgrades/ITS3/simulation/src/ITS3Services.cxx index cc2255a2b2085..6244ea43a5ca8 100644 --- a/Detectors/Upgrades/ITS3/simulation/src/ITS3Services.cxx +++ b/Detectors/Upgrades/ITS3/simulation/src/ITS3Services.cxx @@ -13,17 +13,33 @@ /// \brief Definition of the ITS3Services class /// \author Fabrizio Grosa -#include "ITS3Simulation/ITS3Services.h" +#include +#include +#include -#include // for LOG +#include "ITS3Simulation/ITS3Services.h" +#include "ITS3Base/SpecsV2.h" namespace o2::its3 { void ITS3Services::createCYSSAssembly(TGeoVolume* motherVolume) { - // Return the whole assembly - LOGP(info, "Creating CYSS Assembly and attaching to {}", motherVolume->GetName()); + auto cyssVol = new TGeoVolumeAssembly("IBCYSSAssembly"); + cyssVol->SetVisibility(kTRUE); + motherVolume->AddNode(cyssVol, 1., nullptr); + + // Cylinder + auto cyssInnerCylSh = new TGeoTubeSeg(constants::services::radiusInner, constants::services::radiusOuter, constants::services::length / 2, 180, 360); + auto medRohacell = gGeoManager->GetMedium("IT3_RIST110$"); + auto cyssInnerCylShVol = new TGeoVolume("IBCYSSCylinder", cyssInnerCylSh, medRohacell); + cyssInnerCylShVol->SetLineColor(constants::services::color); + cyssVol->AddNode(cyssInnerCylShVol, 1, new TGeoTranslation(0, 0, 0)); + cyssVol->AddNode(cyssInnerCylShVol, 2, new TGeoCombiTrans(0, 0, 0, new TGeoRotation("", 180, 0, 0))); + + // TODO Cone + // For now the wrapping volume just extends beyond the cylinder if something is added beyond that this volume has to + // be exteneded. } } // namespace o2::its3 diff --git a/Detectors/Upgrades/ITS3/study/CMakeLists.txt b/Detectors/Upgrades/ITS3/study/CMakeLists.txt new file mode 100644 index 0000000000000..314b21c529ebf --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/CMakeLists.txt @@ -0,0 +1,41 @@ +# 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_compile_options(-O0 -g -fPIC -fno-omit-frame-pointer) + +o2_add_library(ITS3TrackingStudy + TARGETVARNAME targetName + SOURCES src/ITS3TrackingStudyParam.cxx + 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 + Eigen3::Eigen + nlohmann_json::nlohmann_json +) + +o2_target_root_dictionary(ITS3TrackingStudy + HEADERS include/ITS3TrackingStudy/ITS3TrackingStudyParam.h + include/ITS3TrackingStudy/ParticleInfoExt.h + LINKDEF src/ITS3TrackingStudyLinkDef.h) + +o2_add_executable(study-workflow + COMPONENT_NAME its3-tracking + SOURCES src/its3-tracking-study-workflow.cxx + PUBLIC_LINK_LIBRARIES O2::ITS3TrackingStudy) + +add_subdirectory(macros) diff --git a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h new file mode 100644 index 0000000000000..1150dc19a1162 --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ITS3TrackingStudyParam.h @@ -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. + +#ifndef O2_TRACKING_STUDY_CONFIG_H +#define O2_TRACKING_STUDY_CONFIG_H +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +#include "DetectorsBase/Propagator.h" + +namespace o2::its3::study +{ + +struct ITS3TrackingStudyParam : o2::conf::ConfigurableParamHelper { + /// general track selection + float maxChi2{36}; + float maxEta{1.5}; + float minPt{0.1}; + float maxPt{1e2}; + /// PV selection + int minPVCont{5}; + /// ITS track selection + int minITSCls{7}; + bool refitITS{true}; // refit ITS track including the PV + bool useStableRef{true}; + bool addPVAsCluster{true}; + /// TPC track selection + int minTPCCls{110}; + + // propagator + o2::base::PropagatorImpl::MatCorrType CorrType = o2::base::PropagatorImpl::MatCorrType::USEMatCorrLUT; + + /// studies + 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"); +}; + +} // namespace o2::its3::study + +#endif diff --git a/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ParticleInfoExt.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ParticleInfoExt.h new file mode 100644 index 0000000000000..c66068418377d --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/ParticleInfoExt.h @@ -0,0 +1,42 @@ +// 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_PARTICLEINFO_EXT_H +#define ALICEO2_PARTICLEINFO_EXT_H + +#include "ReconstructionDataFormats/Track.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "SimulationDataFormat/MCTrack.h" + +namespace o2::its3::study +{ + +struct ParticleInfoExt { + // cluster info + uint8_t clusters{0}; + uint8_t fakeClusters{0}; + // reco info + uint8_t isReco{0}; + uint8_t isFake{0}; + // matching info + uint8_t recoTracks; + uint8_t fakeTracks; + // reco track + track::TrackParCov recoTrack; + // mc info + MCTrack mcTrack; + + ClassDefNV(ParticleInfoExt, 1); +}; + +} // namespace o2::its3::study + +#endif diff --git a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClusterWriterSpec.h b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h similarity index 62% rename from Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClusterWriterSpec.h rename to Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h index 51dc5a6481eb5..6d55dfc80aa8a 100644 --- a/Detectors/ITSMFT/MFT/workflow/include/MFTWorkflow/ClusterWriterSpec.h +++ b/Detectors/Upgrades/ITS3/study/include/ITS3TrackingStudy/TrackingStudy.h @@ -9,23 +9,17 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// @file ClusterWriterSpec.h - -#ifndef O2_MFT_CLUSTERWRITER_H_ -#define O2_MFT_CLUSTERWRITER_H_ +#ifndef O2_ITS3_TRACKING_STUDY_H +#define O2_ITS3_TRACKING_STUDY_H +#include "ReconstructionDataFormats/GlobalTrackID.h" #include "Framework/DataProcessorSpec.h" -namespace o2 -{ -namespace mft +namespace o2::its3::study { -/// create a processor spec -/// write MFT clusters a root file -framework::DataProcessorSpec getClusterWriterSpec(bool useMC); +o2::framework::DataProcessorSpec getTrackingStudySpec(o2::dataformats::GlobalTrackID::mask_t srcTracks, o2::dataformats::GlobalTrackID::mask_t srcClus, bool useMC, bool withPV); -} // namespace mft -} // namespace o2 +} // namespace o2::its3::study -#endif /* O2_MFT_CLUSTERWRITER_H */ +#endif diff --git a/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt b/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt new file mode 100644 index 0000000000000..a772913c51f15 --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/macros/CMakeLists.txt @@ -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. + +o2_add_test_root_macro(PlotDCA.C + PUBLIC_LINK_LIBRARIES O2::ITS3TrackingStudy + LABELS its COMPILE_ONLY) + +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/PlotDCA.C b/Detectors/Upgrades/ITS3/study/macros/PlotDCA.C new file mode 100644 index 0000000000000..ac92fa491c1ac --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/macros/PlotDCA.C @@ -0,0 +1,190 @@ +// 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 PlotDCA.C +/// \brief Simple macro to plot ITS3 impact parameter resolution + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include + +#include +#include +#include +#include +#include +#include + +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "ReconstructionDataFormats/Track.h" +#include "ReconstructionDataFormats/DCA.h" +#include "SimulationDataFormat/MCTrack.h" +#endif + +using GTrackID = o2::dataformats::GlobalTrackID; + +static std::string SanitizeSourceName(std::string_view raw) +{ + std::string s(raw); + s.erase(std::remove(s.begin(), s.end(), '-'), s.end()); + return s; +} + +void PlotDCA(const char* fName = "its3TrackStudy.root") +{ + TH1::SetDefaultSumw2(); + std::unique_ptr inFile(TFile::Open(fName)); + auto tree = inFile->Get("dca"); + + int src; // track type + tree->SetBranchAddress("src", &src); + o2::dataformats::DCA* dca{nullptr}; + tree->SetBranchAddress("dca2MC", &dca); + o2::track::TrackParCov* trk{nullptr}; + tree->SetBranchAddress("trk", &trk); + o2::MCTrack* mcTrk{nullptr}; + tree->SetBranchAddress("mcTrk", &mcTrk); + + const int nPtBins = 35; + const double ptLimits[nPtBins] = {0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2., 2.2, 2.5, 3., 4., 5., 6., 8., 10., 15., 20.}; + const int yDCABins{1000}; + const float yDCARange{500}; + const int nSpecies = 5; + std::array pdgCodes{-1, 11, 211, 321, 2212}; + auto fGaus = new TF1("fGaus", "gaus", -200., 200.); + std::map partNames = { + {-1, "All"}, + {11, "Electrons"}, + {211, "Pions"}, + {321, "Kaons"}, + {2212, "Protons"}}; + + std::map> hMapDCAxyVsPtAllLayers; // species -> [src, {dca}] + std::map> hMapResDCAxyVsPtAllLayers; // species -> [src, {dca}] + std::map> hMapDCAzVsPtAllLayers; // species -> [src, {dca}] + std::map> hMapResDCAzVsPtAllLayers; // species -> [src, {dca}] + std::map> hMapDeltaPtVsPtAllLayers; // species -> [src, {dca}] + std::map> hMapResPtVsPtAllLayers; // species -> [src, {dca}] + for (const auto& [sPDG, sName] : partNames) { + std::map histsDCAxy, histsDCAz, histsDeltaPt; + std::map histsResDCAxy, histsResDCAz, histsResDeltaPt; + + for (int cis = 0; cis < GTrackID::NSources; ++cis) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (!cdm[GTrackID::ITS]) { + continue; // keep same logic as original + } + + const std::string srcRaw = GTrackID::getSourceName(cis); + const std::string src = SanitizeSourceName(srcRaw); + + histsDCAxy[cis] = new TH2F(Form("hDCAxyVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});DCA_{#it{xy}} (#mum);entries", srcRaw.c_str()), nPtBins - 1, ptLimits, yDCABins, -yDCARange, yDCARange); + + histsResDCAxy[cis] = new TH1F(Form("hResDCAxyVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});#sigma(DCA_{#it{xy}}) (#mum);entries", srcRaw.c_str()), nPtBins - 1, ptLimits); + + histsDCAz[cis] = new TH2F(Form("hDCAzVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});DCA_{#it{z}} (#mum);entries", srcRaw.c_str()), nPtBins - 1, ptLimits, yDCABins, -yDCARange, yDCARange); + + histsResDCAz[cis] = new TH1F(Form("hResDCAzVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});#sigma(DCA_{#it{z}}) (#mum);entries", srcRaw.c_str()), nPtBins - 1, ptLimits); + + histsDeltaPt[cis] = new TH2F(Form("hDeltaPtVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});#Delta_{#it{p}_{T}}/#it{p}_{T};entries", srcRaw.c_str()), nPtBins - 1, ptLimits, 200, -0.2, 0.2); + + histsResDeltaPt[cis] = new TH1F(Form("hResDeltaPtVsPtAllLayers_%s_%s", sName.c_str(), src.c_str()), Form("%s;#it{p}_{T,MC} (GeV/#it{c});#sigma(#Delta#it{p}_{T}/#it{p}_{T});entries", srcRaw.c_str()), nPtBins - 1, ptLimits); + } + + hMapDCAxyVsPtAllLayers[sPDG] = std::move(histsDCAxy); + hMapResDCAxyVsPtAllLayers[sPDG] = std::move(histsResDCAxy); + hMapDCAzVsPtAllLayers[sPDG] = std::move(histsDCAz); + hMapResDCAzVsPtAllLayers[sPDG] = std::move(histsResDCAz); + hMapDeltaPtVsPtAllLayers[sPDG] = std::move(histsDeltaPt); + hMapResPtVsPtAllLayers[sPDG] = std::move(histsResDeltaPt); + } + + for (int iEntry = 0; tree->LoadTree(iEntry) >= 0; ++iEntry) { + tree->GetEntry(iEntry); + if (!mcTrk->isPrimary()) { + continue; + } + auto pdg = std::abs(mcTrk->GetPdgCode()); + if (pdg != 11 && pdg != 211 && pdg != 321 && pdg != 2212) { + continue; + } + auto ptReco = trk->getPt(); + auto ptGen = mcTrk->GetPt(); + auto deltaPt = (1. / ptReco - 1. / ptGen) / (1. / ptGen); + auto dcaXY = dca->getY() * 10000.; + auto dcaZ = dca->getZ() * 10000.; + auto phiReco = trk->getPhi(); + + for (int spe : {-1, pdg}) { + hMapDeltaPtVsPtAllLayers[spe][src]->Fill(ptGen, deltaPt); + hMapDCAxyVsPtAllLayers[spe][src]->Fill(ptGen, dcaXY); + hMapDCAzVsPtAllLayers[spe][src]->Fill(ptGen, dcaZ); + } + } + + const char* fitOpt{"QWMER"}; + for (const auto& [sPDG, sName] : partNames) { + for (int cis = 0; cis < GTrackID::NSources; cis++) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (!cdm[GTrackID::ITS]) { + continue; + } + for (auto iPt{0}; iPt < nPtBins; ++iPt) { + auto ptMin = hMapDCAxyVsPtAllLayers[sPDG][cis]->GetXaxis()->GetBinLowEdge(iPt + 1); + float minFit = (ptMin < 1.) ? -200. : -50.; + float maxFit = (ptMin < 1.) ? 200. : 50.; + auto doProjection = [&](auto& hIn, auto& hOut, bool useRange = true) { + auto hProj = hIn[sPDG][cis]->ProjectionY(Form("%s_%d", hOut[sPDG][cis]->GetName(), iPt), iPt + 1, iPt + 1); + if (hProj->GetEntries() < 100) { + return; + } + if (useRange) { + hProj->Fit("fGaus", fitOpt, "", minFit, maxFit); + } else { + hProj->Fit("fGaus", fitOpt); + } + hOut[sPDG][cis]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); + hOut[sPDG][cis]->SetBinError(iPt + 1, fGaus->GetParError(2)); + }; + + doProjection(hMapDeltaPtVsPtAllLayers, hMapResPtVsPtAllLayers, false); + doProjection(hMapDCAxyVsPtAllLayers, hMapResDCAxyVsPtAllLayers); + doProjection(hMapDCAzVsPtAllLayers, hMapResDCAzVsPtAllLayers); + } + } + } + + TFile outFile("plotDCA.root", "RECREATE"); + for (const auto& [sPDG, sName] : partNames) { + outFile.mkdir(sName.c_str()); + outFile.cd(sName.c_str()); + for (int cis = 0; cis < GTrackID::NSources; cis++) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (!cdm[GTrackID::ITS]) { + continue; + } + const std::string srcRaw = GTrackID::getSourceName(cis); + const std::string src = SanitizeSourceName(srcRaw); + gDirectory->mkdir(src.c_str()); + gDirectory->cd(src.c_str()); + + hMapDCAxyVsPtAllLayers[sPDG][cis]->Write(); + hMapResDCAxyVsPtAllLayers[sPDG][cis]->Write(); + hMapDCAzVsPtAllLayers[sPDG][cis]->Write(); + hMapResDCAzVsPtAllLayers[sPDG][cis]->Write(); + hMapDeltaPtVsPtAllLayers[sPDG][cis]->Write(); + hMapResPtVsPtAllLayers[sPDG][cis]->Write(); + + outFile.cd(sName.c_str()); + } + + outFile.cd(); + } +} 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/PlotPulls.C b/Detectors/Upgrades/ITS3/study/macros/PlotPulls.C new file mode 100644 index 0000000000000..371a94cda0e70 --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/macros/PlotPulls.C @@ -0,0 +1,176 @@ +// 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 PlotDCA.C +/// \brief Simple macro to plot ITS3 impact parameter resolution + +#if !defined(__CLING__) || defined(__ROOTCLING__) +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "ReconstructionDataFormats/Track.h" +#include "ReconstructionDataFormats/DCA.h" +#include "SimulationDataFormat/MCTrack.h" +#endif + +// chi2 PDF with amplitude A, degrees of freedom k, scale s +Double_t chi2_pdf(Double_t* x, Double_t* par) +{ + const Double_t xx = x[0]; + const Double_t A = par[0]; + const Double_t k = par[1]; + const Double_t s = par[2]; + if (xx <= 0.0 || k <= 0.0 || s <= 0.0) { + return 0.0; + } + const Double_t coef = 1.0 / (TMath::Power(2.0 * s, k * 0.5) * TMath::Gamma(k * 0.5)); + return A * coef * TMath::Power(xx, (k * 0.5) - 1.0) * TMath::Exp(-xx / (2.0 * s)); +} + +void PlotPulls(const char* fName = "its3TrackStudy.root") +{ + TH1::SetDefaultSumw2(); + std::unique_ptr inFile(TFile::Open(fName)); + auto tree = inFile->Get("pull"); + + uint8_t src; // track type + tree->SetBranchAddress("src", &src); + o2::track::TrackParCov* trk{nullptr}; + tree->SetBranchAddress("trk", &trk); + o2::track::TrackPar* mcTrk{nullptr}; + tree->SetBranchAddress("mcTrk", &mcTrk); + o2::MCTrack* part{nullptr}; + tree->SetBranchAddress("mcPart", &part); + const int nPtBins = 35; + const double ptLimits[nPtBins] = {0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2., 2.2, 2.5, 3., 4., 5., 6., 8., 10., 15., 20.}; + const int yBins{100}, yRange{5}; + const char* pNames[5] = {"Y", "Z", "Snp", "Tgl", "Q2Pt"}; + auto fGaus = new TF1("fGaus", "[0]*exp(-0.5*((x-[1])/[2])**2)", -3., 3.); + + std::array pulls{ + new TH2F("hPullY", "", nPtBins - 1, ptLimits, yBins, -yRange, yRange), + new TH2F("hPullZ", "", nPtBins - 1, ptLimits, yBins, -yRange, yRange), + new TH2F("hPullSnp", "", nPtBins - 1, ptLimits, yBins, -yRange, yRange), + new TH2F("hPullTgl", "", nPtBins - 1, ptLimits, yBins, -yRange, yRange), + new TH2F("hPullQ2Pt", "", nPtBins - 1, ptLimits, yBins, -yRange, yRange)}; + + std::array means{ + new TH1F("hPullYMean", "", nPtBins - 1, ptLimits), + new TH1F("hPullZMean", "", nPtBins - 1, ptLimits), + new TH1F("hPullSnpMean", "", nPtBins - 1, ptLimits), + new TH1F("hPullTglMean", "", nPtBins - 1, ptLimits), + new TH1F("hPullQ2PtMean", "", nPtBins - 1, ptLimits)}; + + std::array sigmas{ + new TH1F("hPullYSigma", "", nPtBins - 1, ptLimits), + new TH1F("hPullZSigma", "", nPtBins - 1, ptLimits), + new TH1F("hPullSnpSigma", "", nPtBins - 1, ptLimits), + new TH1F("hPullTglSigma", "", nPtBins - 1, ptLimits), + new TH1F("hPullQ2PtSigma", "", nPtBins - 1, ptLimits)}; + + auto calcMahalanobisDist2 = [&](const auto* trk, const auto* mc) -> float { + o2::math_utils::SMatrix> cov; + cov(o2::track::kY, o2::track::kY) = trk->getSigmaY2(); + cov(o2::track::kZ, o2::track::kY) = trk->getSigmaZY(); + cov(o2::track::kZ, o2::track::kZ) = trk->getSigmaZ2(); + cov(o2::track::kSnp, o2::track::kY) = trk->getSigmaSnpY(); + cov(o2::track::kSnp, o2::track::kZ) = trk->getSigmaSnpZ(); + cov(o2::track::kSnp, o2::track::kSnp) = trk->getSigmaSnp2(); + cov(o2::track::kTgl, o2::track::kY) = trk->getSigmaTglY(); + cov(o2::track::kTgl, o2::track::kZ) = trk->getSigmaTglZ(); + cov(o2::track::kTgl, o2::track::kSnp) = trk->getSigmaTglSnp(); + cov(o2::track::kTgl, o2::track::kTgl) = trk->getSigmaTgl2(); + cov(o2::track::kQ2Pt, o2::track::kY) = trk->getSigma1PtY(); + cov(o2::track::kQ2Pt, o2::track::kZ) = trk->getSigma1PtZ(); + cov(o2::track::kQ2Pt, o2::track::kSnp) = trk->getSigma1PtSnp(); + cov(o2::track::kQ2Pt, o2::track::kTgl) = trk->getSigma1PtTgl(); + cov(o2::track::kQ2Pt, o2::track::kQ2Pt) = trk->getSigma1Pt2(); + if (!cov.Invert()) { + return -1.f; + } + o2::math_utils::SVector trkPar(trk->getParams(), o2::track::kNParams), mcPar(mc->getParams(), o2::track::kNParams); + auto res = trkPar - mcPar; + return ROOT::Math::Similarity(cov, res); + }; + + auto hMahDist2 = new TH1F("hMahDist2", ";Mahalanobis distance 2;n. entries", 100, 0, 10); + + auto getIndex = [](int i) -> int { return i * (i + 3) / 2; }; + + for (int iEntry = 0; tree->LoadTree(iEntry) >= 0; ++iEntry) { + tree->GetEntry(iEntry); + if (src != o2::dataformats::GlobalTrackID::ITS || std::abs(part->GetPdgCode()) != 211) { + continue; + } + for (int i{0}; i < o2::track::kNParams; ++i) { + pulls[i]->Fill(part->GetPt(), (trk->getParam(i) - mcTrk->getParam(i)) / std::sqrt(trk->getCov()[getIndex(i)])); + } + if (part->GetPt() >= 1.0 && part->GetPt() < 2) { + if (auto dist = calcMahalanobisDist2(trk, mcTrk); dist >= 0.) { + hMahDist2->Fill(dist); + } + } + } + + std::vector projs; + const char* fitOpt{"QWMERSB"}; + for (int i{0}; i < o2::track::kNParams; ++i) { + for (auto iPt{0}; iPt < nPtBins; ++iPt) { + auto hProj = pulls[i]->ProjectionY(Form("%s_%d", pulls[i]->GetName(), iPt), iPt + 1, iPt + 1); + hProj->SetName(Form("p%s_pt%d", pNames[i], iPt)); + hProj->SetTitle(Form("Pull %s #it{p}_{T}#in[%.2f, %.2f)", pNames[i], ptLimits[iPt], ptLimits[iPt + 1])); + projs.push_back(hProj); + if (hProj->GetEntries() < 100) { + return; + } + fGaus->SetParameter(1, 0); + fGaus->SetParameter(2, 1); + auto fRes = hProj->Fit(fGaus, fitOpt); + if (fRes->IsValid() && fGaus->GetParameter(2) > 0) { + means[i]->SetBinContent(iPt + 1, fGaus->GetParameter(1)); + means[i]->SetBinError(iPt + 1, fGaus->GetParError(1)); + sigmas[i]->SetBinContent(iPt + 1, fGaus->GetParameter(2)); + sigmas[i]->SetBinError(iPt + 1, fGaus->GetParError(2)); + } + } + } + + hMahDist2->Scale(1. / hMahDist2->Integral("width")); + TF1* fchi2Fit = new TF1("fchi2_fit", chi2_pdf, 0.1, 6, 3); + fchi2Fit->SetParNames("A", "k", "s"); + fchi2Fit->SetParameter(0, 1); + fchi2Fit->SetParameter(1, 5); + fchi2Fit->SetParameter(2, 1); + auto fitres = hMahDist2->Fit(fchi2Fit, "RMQS"); + fitres->Print(); + + TFile outFile("plotPulls.root", "RECREATE"); + for (int i{0}; i < o2::track::kNParams; ++i) { + pulls[i]->Write(); + means[i]->Write(); + sigmas[i]->Write(); + } + for (const auto& p : projs) { + p->Write(); + } + hMahDist2->Write(); + fchi2Fit->Write(); +} 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/ITSMFT/ITS/reconstruction/src/CookedTrackerLinkDef.h b/Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyLinkDef.h similarity index 68% rename from Detectors/ITSMFT/ITS/reconstruction/src/CookedTrackerLinkDef.h rename to Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyLinkDef.h index 1e8596fbb224c..182ffd858629c 100644 --- a/Detectors/ITSMFT/ITS/reconstruction/src/CookedTrackerLinkDef.h +++ b/Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyLinkDef.h @@ -15,12 +15,9 @@ #pragma link off all classes; #pragma link off all functions; -#pragma link C++ class o2::its::ClustererTask + ; -#pragma link C++ class o2::its::CookedTracker + ; -#pragma link C++ class o2::its::CookedConfigParam + ; -#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its::CookedConfigParam> + ; -#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::its3::study::ITS3TrackingStudyParam + ; +#pragma link C++ class o2::conf::ConfigurableParamHelper < o2::its3::study::ITS3TrackingStudyParam> + ; + +#pragma link C++ class o2::its3::study::ParticleInfoExt + ; #endif diff --git a/Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyParam.cxx b/Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyParam.cxx new file mode 100644 index 0000000000000..00bb800e65f8c --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/src/ITS3TrackingStudyParam.cxx @@ -0,0 +1,13 @@ +// 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 "ITS3TrackingStudy/ITS3TrackingStudyParam.h" +O2ParamImpl(o2::its3::study::ITS3TrackingStudyParam); diff --git a/Detectors/Upgrades/ITS3/study/src/ParticleInfoExt.cxx b/Detectors/Upgrades/ITS3/study/src/ParticleInfoExt.cxx new file mode 100644 index 0000000000000..aa5edbf408270 --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/src/ParticleInfoExt.cxx @@ -0,0 +1,13 @@ +// 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 "ITS3TrackingStudy/ParticleInfoExt.h" +ClassImp(o2::its3::study::ParticleInfoExt); diff --git a/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx new file mode 100644 index 0000000000000..63a08d1c9c6ab --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/src/TrackingStudy.cxx @@ -0,0 +1,1186 @@ +// 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 "CommonUtils/TreeStreamRedirector.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" +#include "DataFormatsITSMFT/Digit.h" +#include "ITSMFTSimulation/Hit.h" +#include "DetectorsBase/GeometryManager.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "DetectorsBase/Propagator.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "DetectorsCommonDataFormats/SimTraits.h" +#include "DetectorsVertexing/PVertexer.h" +#include "Framework/CCDBParamSpec.h" +#include "Framework/DeviceSpec.h" +#include "Framework/Task.h" +#include "ITSBase/GeometryTGeo.h" +#include "ITS3Base/SpecsV2.h" +#include "ITS3Reconstruction/TopologyDictionary.h" +#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" +#include "ReconstructionDataFormats/PrimaryVertexExt.h" +#include "ReconstructionDataFormats/VtxTrackRef.h" +#include "SimulationDataFormat/MCEventLabel.h" +#include "SimulationDataFormat/MCUtils.h" +#include "Steer/MCKinematicsReader.h" +#include "Framework/Logger.h" + +namespace o2::its3::study +{ + +using namespace o2::framework; +using DetID = o2::detectors::DetID; +using DataRequest = o2::globaltracking::DataRequest; +using PVertex = o2::dataformats::PrimaryVertex; +using GTrackID = o2::dataformats::GlobalTrackID; +using VtxTrackID = o2::dataformats::VtxTrackIndex; +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; + void init(InitContext& ic) final; + void run(ProcessingContext& pc) final; + void endOfStream(EndOfStreamContext& ec) final; + void finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) final; + + private: + void process(); + void updateTimeDependentParams(ProcessingContext& pc); + 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; + + void operator+=(int src) + { + if (src >= 0 && src < static_cast(mSuccess.size())) { + ++mSuccess[src]; + } + } + + void operator-=(int src) + { + if (src >= 0 && src < static_cast(mirrors.size())) { + ++mirrors[src]; + } + } + + void operator&=(int src) + { + if (src >= 0 && src < static_cast(mRejected.size())) { + ++mRejected[src]; + } + } + + void print() const + { + LOGP(info, "\t\t\tSuccess / Error / Rejected"); + for (int cis = 0; cis < GTrackID::NSources; ++cis) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (cdm[DetID::ITS]) { + LOGP(info, "\t{:{}}\t{} / {} / {}", GTrackID::getSourceName(cis), 15, mSuccess[cis], mirrors[cis], mRejected[cis]); + } + } + } + + void reset() + { + mSuccess.fill(0); + mirrors.fill(0); + mRejected.fill(0); + } + + std::array mSuccess{}; + std::array mirrors{}; + std::array mRejected{}; + }; + 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 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) +{ + o2::base::GRPGeomHelper::instance().setRequest(mGGCCDBRequest); + int lane = ic.services().get().inputTimesliceId; + int maxLanes = ic.services().get().maxInputTimeslices; + std::string dbgnm = maxLanes == 1 ? "its3TrackStudy.root" : fmt::format("its3TrackStudy_{}.root", lane); + mDBGOut = std::make_unique(dbgnm.c_str(), "recreate"); + + if (mUseMC && !mMCReader.initFromDigitContext(o2::base::NameConf::getCollisionContextFileName())) { + LOGP(fatal, "initialization of MCKinematicsReader failed"); + } +} + +void TrackingStudySpec::run(ProcessingContext& pc) +{ + mRecoData.collectData(pc, *mDataRequest); + updateTimeDependentParams(pc); + process(); +} + +void TrackingStudySpec::updateTimeDependentParams(ProcessingContext& pc) +{ + o2::base::GRPGeomHelper::instance().checkUpdates(pc); + if (static bool initOnceDone{false}; !initOnceDone) { // this params need to be queried only once + initOnceDone = true; + 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); + } + } + } +} + +void TrackingStudySpec::endOfStream(EndOfStreamContext& ec) +{ + mDBGOut.reset(); +} + +void TrackingStudySpec::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (o2::base::GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { + return; + } + if (matcher == ConcreteDataMatcher("IT3", "CLUSDICT", 0)) { + LOG(info) << "cluster dictionary updated"; + mITSDict = (const o2::its3::TopologyDictionary*)obj; + return; + } +} + +void TrackingStudySpec::process() +{ + prepareITSClusters(); + if (mParams->doDCA) { + doDCAStudy(); + } + if (mParams->doDCARefit) { + doDCARefitStudy(); + } + if (mUseMC && mParams->doPull) { + doPullStudy(); + } + if (mUseMC && mParams->doMC) { + doMCStudy(); + } + if (mParams->doResid) { + doResidStudy(); + } + if (mUseMC && mParams->doMisalignment) { + doMisalignmentStudy(); + } +} + +void TrackingStudySpec::prepareITSClusters() +{ + 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, bool checkMCTruth) const +{ + if (!trkID.includesDet(GTrackID::ITS)) { + return false; + } + if (!mRecoData.isTrackSourceLoaded(trkID.getSource())) { + return false; + } + auto contributorsGID = mRecoData.getSingleDetectorRefs(trkID); + if (!contributorsGID[GTrackID::ITS].isIndexSet()) { // we need of course ITS + return false; + } + // ITS specific + 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 = mRecoData.getTPCTrack(contributorsGID[GTrackID::TPC]); + if (tpcTrk.getNClusters() < mParams->minTPCCls) { + return false; + } + } + // general + const auto& gTrk = mRecoData.getTrackParam(trkID); + if (gTrk.getPt() < mParams->minPt || gTrk.getPt() > mParams->maxPt) { + return false; + } + if (std::abs(gTrk.getEta()) > mParams->maxEta) { + return false; + } + if (mUseMC && checkMCTruth) { + const auto& itsLbl = mRecoData.getTrackMCLabel(contributorsGID[GTrackID::ITS]); + if (!itsLbl.isValid()) { + return false; + } + if (contributorsGID[GTrackID::TPC].isIndexSet()) { + const auto& tpcLbl = mRecoData.getTrackMCLabel(contributorsGID[GTrackID::TPC]); + if (itsLbl != tpcLbl) { + return false; + } + } + if (contributorsGID[GTrackID::TRD].isIndexSet()) { + // TODO + } + if (contributorsGID[GTrackID::TOF].isIndexSet()) { + const auto& tofLbls = mRecoData.getTOFClustersMCLabels()->getLabels(contributorsGID[GTrackID::TOF]); + for (const auto& lbl : tofLbls) { + if (lbl.isValid()) { + return true; + } + } + } + } + return true; +} + +T2VMap TrackingStudySpec::buildT2V(bool includeCont, bool requireMCMatch) const +{ + // build track->vertex assoc., maybe including contributor tracks + 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 < mParams->minPVCont) { + continue; + } + if (requireMCMatch) { + auto pvl = mRecoData.getPrimaryVertexMCLabel(iv); + } + const auto& vtxRef = vtxRefs[iv]; + int it = vtxRef.getFirstEntry(), itLim = it + vtxRef.getEntries(); + for (; it < itLim; it++) { + const auto& tvid = trackIndex[it]; + if (tvid.isAmbiguous()) { + continue; + } + if (!mRecoData.isTrackSourceLoaded(tvid.getSource())) { + continue; + } + if (mUseMC && requireMCMatch) { + const auto& pvlbl = mRecoData.getPrimaryVertexMCLabel(iv); + if (pvlbl.getEventID() != mRecoData.getTrackMCLabel(tvid).getEventID()) { + continue; + } + } + t2v[tvid] = iv; + if (includeCont) { + auto contributorsGID = mRecoData.getSingleDetectorRefs(tvid); + for (int cis = 0; cis < GTrackID::NSources; cis++) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (!mRecoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { + continue; + } + if (mUseMC && requireMCMatch) { + const auto& pvlbl = mRecoData.getPrimaryVertexMCLabel(iv); + if (pvlbl.getEventID() != mRecoData.getTrackMCLabel(contributorsGID[cis]).getEventID()) { + continue; + } + } + t2v[contributorsGID[cis]] = iv; + } + } + } + } + return std::move(t2v); +} + +bool TrackingStudySpec::refitITSPVTrack(o2::track::TrackParCov& trFit, GTrackID gidx) +{ + if (gidx.getSource() != GTrackID::ITS) { + return false; + } + 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 = 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++) { + itsTracksROF[cnt++] = irf; + } + } + } + auto prop = o2::base::Propagator::Instance(); + 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 = 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, mParams->CorrType)) { + mTrackCounter -= gidx.getSource(); + return false; + } + // create base cluster from the PV, with the alpha corresponding to the track at DCA + float cosAlp = NAN, sinAlp = NAN; + o2::math_utils::sincos(trkPV.getAlpha(), sinAlp, cosAlp); + // vertex position rotated to track frame + 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] = &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(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])) { + mTrackCounter -= gidx.getSource(); + return false; + } + } + // chi2 < conf.maxChi2; should I cut here? + return true; +}; + +void TrackingStudySpec::doDCAStudy() +{ + /// analyse DCA of impact parameter for different track types + LOGP(info, "Doing DCA study"); + mTrackCounter.reset(); + auto prop = o2::base::Propagator::Instance(); + TStopwatch sw; + sw.Start(); + int nDCAFits{0}, nDCAFitsFail{0}; + 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 (!mRecoData.isTrackSourceLoaded(is) || !dm[DetID::ITS]) { + mTrackCounter &= is; + continue; + } + int idMin = vtref.getFirstEntryOfSource(is), idMax = idMin + vtref.getEntriesOfSource(is); + for (int i = idMin; i < idMax; i++) { + const auto vid = trackIndex[i]; + if (!vid.isPVContributor()) { + mTrackCounter &= vid.getSource(); + continue; + } + + // 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 = mRecoData.getSingleDetectorRefs(vid); + for (int cis = 0; cis < GTrackID::NSources && cis <= is; cis++) { + const auto cdm = GTrackID::getSourceDetectorsMask(cis); + if (!mRecoData.isTrackSourceLoaded(cis) || !cdm[DetID::ITS] || !contributorsGID[cis].isIndexSet()) { + mTrackCounter &= cis; + continue; + } + if (!selectTrack(contributorsGID[cis])) { + mTrackCounter &= vid.getSource(); + continue; + } + + o2::dataformats::DCA dcaInfo; + 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 (mParams->refitITS && cis == GTrackID::ITS && !refitITSPVTrack(trkRefit, contributorsGID[cis])) { + mTrackCounter -= cis; + continue; + } else { + trkRefit.invalidate(); + }; + + auto trkDCA = trk; + if (!prop->propagateToDCABxByBz(pv, trkDCA, 2.f, mParams->CorrType, &dcaInfo)) { + mTrackCounter -= cis; + ++nDCAFitsFail; + continue; + } + + stream << "src=" << cis + << "pv=" << pv + << "trk=" << trk + << "trkRefit=" << trkRefit + << "trkAtPV=" << trkDCA + << "dca=" << dcaInfo; + + if (mUseMC) { + const auto& lbl = mRecoData.getTrackMCLabel(contributorsGID[cis]); + lbl.print(); + o2::dataformats::DCA dcaInfoMC; + 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, mParams->CorrType, &dcaInfoMC)) { + mTrackCounter -= cis; + ++nDCAFitsFail; + continue; + } + const auto& mcTrk = mMCReader.getTrack(lbl); + if (mcTrk == nullptr) { + LOGP(fatal, "mcTrk is null did selection fail?"); + } + stream << "mcTrk=" << *mcTrk + << "dca2MC=" << dcaInfoMC + << "lbl=" << lbl; + } + stream << "\n"; + + ++nDCAFits; + mTrackCounter += cis; + } + } + } + } + sw.Stop(); + LOGP(info, "doDCAStudy: accepted {} fits, failed {} (in {:.2f} seconds)", nDCAFits, nDCAFitsFail, sw.RealTime()); + mTrackCounter.print(); +} + +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(); + auto prop = o2::base::Propagator::Instance(); + TStopwatch sw; + sw.Start(); + + // build track->vertex assoc. + 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 { + if constexpr (!isBarrelTrack()) { + mTrackCounter &= trkID.getSource(); + return false; + } + if (!trkID.includesDet(GTrackID::ITS)) { + mTrackCounter &= trkID.getSource(); + return false; + } + // general + if constexpr (isBarrelTrack()) { + if (trk.getPt() < mParams->minPt || trk.getPt() > mParams->maxPt) { + mTrackCounter &= trkID.getSource(); + return false; + } + if (std::abs(trk.getEta()) > mParams->maxEta) { + mTrackCounter &= trkID.getSource(); + return false; + } + if (!t2v.contains(trkID)) { + mTrackCounter &= trkID.getSource(); + return false; + } + if (!selectTrack(trkID, mUseMC)) { + mTrackCounter &= trkID.getSource(); + return false; + } + } + v2t[t2v[trkID]].push_back(trkID); + return true; + }; + 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 < mParams->minPVCont) { + continue; + } + std::vector trks; + trks.reserve(trkIDs.size()); + for (const auto& trkID : trkIDs) { + trks.push_back(mRecoData.getTrackParam(trkID)); + } + + if (!mVertexer.prepareVertexRefit(trks, pv)) { + continue; + } + std::vector trkMask(trkIDs.size(), true); + for (size_t it{0}; it < trkMask.size(); ++it) { + trkMask[it] = false; // mask current track from pv refit + if (it != 0) { + trkMask[it - 1] = true; // unmask previoustrack from pv refit + } + auto pvRefit = mVertexer.refitVertex(trkMask, pv); + if (pvRefit.getChi2() < 0) { + trkMask[it] = true; + continue; + } + + // check DCA both for refitted and original PV + o2::dataformats::DCA dcaInfo; + auto trkC = trks[it]; + 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, mParams->CorrType, &dcaInfoRefit)) { + mTrackCounter -= trkIDs[it].getSource(); + ++nDCAFitsFail; + continue; + } + + stream << "src=" << trkIDs[it].getSource() + << "pv=" << pv + << "trkAtPV=" << trkC + << "dca=" << dcaInfo + << "pvRefit=" << pvRefit + << "trkAtPVRefit=" << trkC + << "dcaRefit=" << dcaInfoRefit; + if (mUseMC) { + const auto& mcTrk = mMCReader.getTrack(mRecoData.getTrackMCLabel(trkIDs[it])); + if (mcTrk == nullptr) { + LOGP(fatal, "mcTrk is null did selection fail?"); + } + stream << "mcTrk=" << *mcTrk; + } + stream << "\n"; + ++nDCAFits; + mTrackCounter += trkIDs[it].getSource(); + } + } + sw.Stop(); + LOGP(info, "doDCARefitStudy: accepted {} fits, failed {} (in {:.2f} seconds)", nDCAFits, nDCAFitsFail, sw.RealTime()); + mTrackCounter.print(); +} + +void TrackingStudySpec::doPullStudy() +{ + // check track pulls compared to mc generation + LOGP(info, "Doing Pull study"); + mTrackCounter.reset(); + TStopwatch sw; + sw.Start(); + int nPulls{0}, nPullsFail{0}; + auto prop = o2::base::Propagator::Instance(); + + auto checkInTrack = [&](GTrackID trkID) { + if (!selectTrack(trkID)) { + mTrackCounter &= trkID.getSource(); + return; + } + const auto mcTrk = mMCReader.getTrack(mRecoData.getTrackMCLabel(trkID)); + if (!mcTrk) { + return; + } + auto trk = mRecoData.getTrackParam(trkID); + + // for ITS standalone tracks we add the PV as an additional measurement point + if (mParams->refitITS && trkID.getSource() == GTrackID::ITS && !refitITSPVTrack(trk, trkID)) { + mTrackCounter -= trkID.getSource(); + ++nPullsFail; + return; + } + + std::array xyz{(float)mcTrk->GetStartVertexCoordinatesX(), (float)mcTrk->GetStartVertexCoordinatesY(), (float)mcTrk->GetStartVertexCoordinatesZ()}, + pxyz{(float)mcTrk->GetStartVertexMomentumX(), (float)mcTrk->GetStartVertexMomentumY(), (float)mcTrk->GetStartVertexMomentumZ()}; + TParticlePDG* pPDG = TDatabasePDG::Instance()->GetParticle(mcTrk->GetPdgCode()); + if (!pPDG) { + mTrackCounter -= trkID.getSource(); + ++nPullsFail; + return; + } + o2::track::TrackPar mcTrkO2(xyz, pxyz, TMath::Nint(pPDG->Charge() / 3), false); + // propagate it to the alpha/X of the reconstructed track + if (!mcTrkO2.rotate(trk.getAlpha()) || !prop->PropagateToXBxByBz(mcTrkO2, trk.getX())) { + mTrackCounter -= trkID.getSource(); + ++nPullsFail; + return; + } + const auto contTrk = mRecoData.getSingleDetectorRefs(trkID); + const auto& itsTrk = mRecoData.getITSTrack(contTrk[GTrackID::ITS]); + + (*mDBGOut) + << "pull" + << "src=" << trkID.getSource() + << "itsTrk=" << itsTrk + << "mcTrk=" << mcTrkO2 + << "mcPart=" << mcTrk + << "trk=" << trk + << "\n"; + ++nPulls; + mTrackCounter += trkID.getSource(); + }; + + for (size_t iTrk{0}; iTrk < mRecoData.getITSTracks().size(); ++iTrk) { + checkInTrack(GTrackID(iTrk, GTrackID::ITS)); + } + for (size_t iTrk{0}; iTrk < mRecoData.getTPCITSTracks().size(); ++iTrk) { + checkInTrack(GTrackID(iTrk, GTrackID::ITSTPC)); + } + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTRDTracksMCLabels().size(); ++iTrk) { + checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTRD)); + } + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTOFMatches().size(); ++iTrk) { + checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTOF)); + } + for (size_t iTrk{0}; iTrk < mRecoData.getITSTPCTRDTOFMatches().size(); ++iTrk) { + checkInTrack(GTrackID(iTrk, GTrackID::ITSTPCTRDTOF)); + } + sw.Stop(); + LOGP(info, "doPullStudy: accepted {} pulls; rejected {} (in {:.2f} seconds)", nPulls, nPullsFail, sw.RealTime()); + mTrackCounter.print(); +} + +void TrackingStudySpec::doMCStudy() +{ + LOGP(info, "Doing MC study"); + mTrackCounter.reset(); + TStopwatch sw; + sw.Start(); + int nTracks{0}; + + const int iSrc{0}; + 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 = mMCReader.getTracks(iSrc, iEve); + for (int iTrk{0}; iTrk < mcTrks.size(); ++iTrk) { + const auto& mcTrk = mcTrks[iTrk]; + const auto pdg = mcTrk.GetPdgCode(); + if (o2::O2DatabasePDG::Instance()->GetParticle(pdg) == nullptr) { + continue; + } + const auto apdg = std::abs(pdg); + if (apdg != 11 && apdg != 211 && apdg != 321 && apdg != 2212) { + continue; + } + o2::MCCompLabel lbl(iTrk, iEve, iSrc); + auto& part = info[lbl]; + part.mcTrack = mcTrk; + } + } + LOGP(info, "** Creating particle/clusters correspondence ... "); + 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) { + if (!lab.isValid() || lab.getSourceID() != 0 || !lab.isCorrect()) { + continue; + } + int trackID = 0, evID = 0, srcID = 0; + bool fake = false; + lab.get(trackID, evID, srcID, fake); + auto& cluster = clusters[iCluster]; + auto layer = o2::its::GeometryTGeo::Instance()->getLayer(cluster.getSensorID()); + auto& part = info[{trackID, evID, srcID}]; + part.clusters |= (1 << layer); + if (fake) { + part.fakeClusters |= (1 << layer); + } + } + } + LOGP(info, "** Analysing tracks ... "); + auto accountLbl = [&](const globaltracking::RecoContainer::GlobalIDSet& contributorsGID, DetID::ID det) { + if (contributorsGID[det].isIndexSet()) { + const auto& lbl = mRecoData.getTrackMCLabel(contributorsGID[det]); + if (lbl.isValid()) { + o2::MCCompLabel iLbl(lbl.getTrackID(), lbl.getEventID(), lbl.getSourceID()); + if (info.contains(iLbl)) { + auto& part = info[iLbl]; + SETBIT(part.recoTracks, det); + if (lbl.isFake()) { + SETBIT(part.fakeTracks, det); + } + } + } + } + }; + auto creator = [&](const auto& trk, GTrackID trkID, float _t0, float terr) -> bool { + if constexpr (!isBarrelTrack()) { + return false; + } + if (!trkID.includesDet(GTrackID::ITS)) { + return false; + } + // general + auto contributorsGID = mRecoData.getSingleDetectorRefs(trkID); + if (!contributorsGID[GTrackID::ITS].isIndexSet()) { // we need of course ITS + return false; + } + const auto& gLbl = mRecoData.getTrackMCLabel(trkID); + if (!gLbl.isValid()) { + return false; + } + o2::MCCompLabel iLbl(gLbl.getTrackID(), gLbl.getEventID(), gLbl.getSourceID()); + if (!info.contains(iLbl)) { + return false; + } + auto& part = info[iLbl]; + part.recoTrack = mRecoData.getTrackParam(trkID); + + accountLbl(contributorsGID, DetID::ITS); + accountLbl(contributorsGID, DetID::TPC); + accountLbl(contributorsGID, DetID::TRD); + accountLbl(contributorsGID, DetID::TOF); + + ++nTracks; + return true; + }; + mRecoData.createTracksVariadic(creator); + + LOGP(info, "Streaming output to tree"); + for (const auto& [_, part] : info) { + (*mDBGOut) << "mc" + << "part=" << part + << "\n"; + } + + sw.Stop(); + LOGP(info, "doMCStudy: accounted {} MCParticles and {} tracks (in {:.2f} seconds)", info.size(), nTracks, sw.RealTime()); +} + +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(); + + dataRequest->requestTracks(srcTracks, useMC); + dataRequest->requestIT3Clusters(useMC); + dataRequest->requestClusters(srcClusters, useMC); + if (withPV) { + 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); + + return DataProcessorSpec{ + .name = "its3-track-study", + .inputs = dataRequest->inputs, + .outputs = outputs, + .algorithm = AlgorithmSpec{adaptFromTask(dataRequest, ggRequest, srcTracks, useMC)}, + .options = {}}; +} + +} // namespace o2::its3::study diff --git a/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx b/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx new file mode 100644 index 0000000000000..482ef2bb71e1d --- /dev/null +++ b/Detectors/Upgrades/ITS3/study/src/its3-tracking-study-workflow.cxx @@ -0,0 +1,77 @@ +// 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 "ITS3TrackingStudy/TrackingStudy.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DetectorsCommonDataFormats/DetID.h" +#include "CommonUtils/ConfigurableParam.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/ConfigParamSpec.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "Framework/CallbacksPolicy.h" +#include "DetectorsBase/DPLWorkflowUtils.h" +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "DetectorsRaw/HBFUtilsInitializer.h" + +using namespace o2::framework; +using GID = o2::dataformats::GlobalTrackID; +using DetID = o2::detectors::DetID; + +// ------------------------------------------------------------------ +void customize(std::vector& policies) +{ + o2::raw::HBFUtilsInitializer::addNewTimeSliceCallback(policies); +} + +// we need to add workflow options before including Framework/runDataProcessing +void customize(std::vector& workflowOptions) +{ + // option allowing to set parameters + std::vector options{ + {"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); + std::swap(workflowOptions, options); +} + +// ------------------------------------------------------------------ + +#include "Framework/runDataProcessing.h" + +WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) +{ + WorkflowSpec specs; + + GID::mask_t allowedSourcesTrc = GID::getSourcesMask("ITS,TPC,TRD,TOF,ITS-TPC,ITS-TPC-TRD,ITS-TPC-TOF,ITS-TPC-TRD-TOF"); + GID::mask_t allowedSourcesClus = GID::getSourcesMask("ITS,TPC,TRD,TOF"); + + 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); + if (usePV) { + o2::globaltracking::InputHelper::addInputSpecsPVertex(configcontext, specs, useMC); + } + + specs.emplace_back(o2::its3::study::getTrackingStudySpec(srcTrc, srcCls, useMC, usePV)); + + o2::raw::HBFUtilsInitializer hbfIni(configcontext, specs); + + return std::move(specs); +} diff --git a/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/RecoWorkflow.h b/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/RecoWorkflow.h index 592a34d94a3ca..010e1cd0a8127 100644 --- a/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/RecoWorkflow.h +++ b/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/RecoWorkflow.h @@ -16,16 +16,14 @@ #include "Framework/WorkflowSpec.h" #include "ITStracking/Configuration.h" -#include "GPUO2Interface.h" -#include "GPUReconstruction.h" -#include "GPUChainITS.h" +#include "GPUDataTypesConfig.h" namespace o2::its3::reco_workflow { framework::WorkflowSpec getWorkflow(bool useMC, its::TrackingMode::Type trmode, - o2::gpu::GPUDataTypes::DeviceType dtype, + o2::gpu::gpudatatypes::DeviceType dtype, bool useGPUWorkflow, bool upstreamDigits, bool upstreamClusters, diff --git a/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/TrackerSpec.h b/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/TrackerSpec.h index 42f71b6ccebe0..66d58a5f5a92c 100644 --- a/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/TrackerSpec.h +++ b/Detectors/Upgrades/ITS3/workflow/include/ITS3Workflow/TrackerSpec.h @@ -23,11 +23,17 @@ #include "ITS3Reconstruction/TrackingInterface.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesConfig.h" #include "DetectorsBase/GRPGeomHelper.h" #include "TStopwatch.h" +namespace o2::gpu +{ +class GPUReconstruction; +class GPUChainITS; +} // namespace o2::gpu + namespace o2::its3 { @@ -39,7 +45,7 @@ class TrackerDPL : public framework::Task int trgType, its::TrackingMode::Type trmode = its::TrackingMode::Unset, const bool overrBeamEst = false, - gpu::GPUDataTypes::DeviceType dType = gpu::GPUDataTypes::DeviceType::CPU); + gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); ~TrackerDPL() override = default; TrackerDPL(const TrackerDPL&) = delete; TrackerDPL(TrackerDPL&&) = delete; @@ -63,7 +69,7 @@ class TrackerDPL : public framework::Task /// create a processor spec /// run ITS CA tracker -framework::DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int useTrig, its::TrackingMode::Type trMode, const bool overrBeamEst = false, gpu::GPUDataTypes::DeviceType dType = gpu::GPUDataTypes::DeviceType::CPU); +framework::DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int useTrig, its::TrackingMode::Type trMode, const bool overrBeamEst = false, gpu::gpudatatypes::DeviceType dType = gpu::gpudatatypes::DeviceType::CPU); } // namespace o2::its3 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 8a1c1ef73cf2b..f27fda19fe00c 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/RecoWorkflow.cxx @@ -12,7 +12,7 @@ #include "ITS3Workflow/RecoWorkflow.h" #include "ITS3Workflow/ClustererSpec.h" #include "ITS3Workflow/TrackerSpec.h" -#include "ITSWorkflow/ClusterWriterSpec.h" +#include "ITSMFTWorkflow/ClusterWriterSpec.h" #include "ITSWorkflow/TrackWriterSpec.h" #include "ITS3Workflow/DigitReaderSpec.h" #include "GPUWorkflow/GPUWorkflowSpec.h" @@ -26,7 +26,7 @@ static std::shared_ptr gTask; namespace o2::its3::reco_workflow { -framework::WorkflowSpec getWorkflow(bool useMC, its::TrackingMode::Type trmode, o2::gpu::GPUDataTypes::DeviceType dtype, bool useGPUWorkflow, +framework::WorkflowSpec getWorkflow(bool useMC, its::TrackingMode::Type trmode, o2::gpu::gpudatatypes::DeviceType dtype, bool useGPUWorkflow, bool upstreamDigits, bool upstreamClusters, bool disableRootOutput, bool useGeom, int useTrig, bool overrideBeamPosition) { framework::WorkflowSpec specs; @@ -40,7 +40,7 @@ framework::WorkflowSpec getWorkflow(bool useMC, its::TrackingMode::Type trmode, } if (!disableRootOutput) { - specs.emplace_back(o2::its::getClusterWriterSpec(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 7945f8e0af1df..8db02d7227e7f 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/TrackerSpec.cxx @@ -23,24 +23,17 @@ #include "DataFormatsITSMFT/PhysTrigger.h" #include "ITStracking/TrackingConfigParam.h" -#include "ITSMFTBase/DPLAlpideParam.h" +#include "DataFormatsITSMFT/DPLAlpideParam.h" #include "ITSBase/GeometryTGeo.h" #include "CommonDataFormat/IRFrame.h" #include "DataFormatsTRD/TriggerRecord.h" #include "ITS3Reconstruction/IOUtils.h" -#include "ITSReconstruction/FastMultEstConfig.h" #include "ITS3Base/SpecsV2.h" namespace o2 { using namespace framework; -using its::FastMultEstConfig; -using its::TimeFrame; -using its::Tracker; -using its::TrackingParameters; -using its::TrackITSExt; -using its::Vertexer; namespace its3 { @@ -51,9 +44,9 @@ TrackerDPL::TrackerDPL(std::shared_ptr gr, int trgType, its::TrackingMode::Type trMode, const bool overrBeamEst, - o2::gpu::GPUDataTypes::DeviceType dType) : mGGCCDBRequest(gr), + 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); } @@ -95,7 +88,7 @@ void TrackerDPL::endOfStream(EndOfStreamContext& ec) LOGF(info, "ITS3 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, its::TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::GPUDataTypes::DeviceType dType) +DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int trgType, its::TrackingMode::Type trMode, const bool overrBeamEst, o2::gpu::gpudatatypes::DeviceType dType) { std::vector inputs; inputs.emplace_back("compClusters", "ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); diff --git a/Detectors/Upgrades/ITS3/workflow/src/its3-reco-workflow.cxx b/Detectors/Upgrades/ITS3/workflow/src/its3-reco-workflow.cxx index e4c78b3323a5e..018ad807a974a 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/its3-reco-workflow.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/its3-reco-workflow.cxx @@ -63,7 +63,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const& configcontext) auto beamPosOVerride = configcontext.options().get("ccdb-meanvertex-seed"); auto trmode = configcontext.options().get("tracking-mode"); auto selTrig = configcontext.options().get("select-with-triggers"); - auto gpuDevice = static_cast(configcontext.options().get("gpu-device")); + 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"); diff --git a/Detectors/Vertexing/include/DetectorsVertexing/FwdDCAFitterN.h b/Detectors/Vertexing/include/DetectorsVertexing/FwdDCAFitterN.h deleted file mode 100644 index cd1742e24fa72..0000000000000 --- a/Detectors/Vertexing/include/DetectorsVertexing/FwdDCAFitterN.h +++ /dev/null @@ -1,1297 +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 FwdDCAFitterN.h -/// \brief Defintions for N-prongs secondary vertex fit -/// \author ruben.shahoyan@cern.ch, adapted from central barrel to fwd rapidities by Rita Sadek, rita.sadek@cern.ch -/// For the formulae derivation see /afs/cern.ch/user/s/shahoian/public/O2/DCAFitter/DCAFitterN.pdf - -#ifndef _ALICEO2_DCA_FWDFITTERN_ -#define _ALICEO2_DCA_FWDFITTERN_ -#include -#include "MathUtils/Cartesian.h" -#include "ReconstructionDataFormats/TrackFwd.h" -#include "ReconstructionDataFormats/Track.h" -#include "DCAFitter/HelixHelper.h" -#include -#include "DetectorsBase/Propagator.h" -#include "DetectorsBase/GeometryManager.h" - -namespace o2 -{ -namespace vertexing -{ - -///__________________________________________________________________________________ -///< Fwd Inverse cov matrix (augmented by a dummy Z error) of the point defined by the track -struct FwdTrackCovI { - float sxx, syy, sxy, szz; - - FwdTrackCovI(const o2::track::TrackParCovFwd& trc, float zerrFactor = 1.) { set(trc, zerrFactor); } - FwdTrackCovI() = default; - void set(const o2::track::TrackParCovFwd& trc, float zerrFactor = 1) - { - float cxx = trc.getSigma2X(), cyy = trc.getSigma2Y(), cxy = trc.getSigmaXY(), czz = cyy * zerrFactor; - float detXY = cxx * cyy - cxy * cxy; - if (detXY > 0.) { - auto detXYI = 1. / detXY; - sxx = cyy * detXYI; - syy = cxx * detXYI; - sxy = -cxy * detXYI; - szz = 1. / czz; - } else { - throw std::runtime_error("invalid track covariance"); - } - } -}; - -///__________________________________________________________________________ -///< Fwd derivative (up to 2) of the TrackParam position over its running param Z -struct FwdTrackDeriv { - float dxdz, dydz, d2xdz2, d2ydz2; - FwdTrackDeriv() = default; - FwdTrackDeriv(const o2::track::TrackParFwd& trc, float bz) { set(trc, bz); } - void set(const o2::track::TrackParFwd& trc, float bz) - { - float snp = trc.getSnp(), csp = std::sqrt((1. - snp) * (1. + snp)), cspI = 1. / csp, crv2c = trc.getCurvature(bz), tgl = trc.getTanl(), tglI = 1. / tgl; - if (crv2c == 0.) { - crv2c = (trc.getCharge()) * 0.3 * bz * (-1e-3); - } - - dxdz = csp * tglI; - dydz = snp * tglI; - d2xdz2 = crv2c * snp * tglI * tglI; - d2ydz2 = -crv2c * csp * tglI * tglI; - } -}; - -template -class FwdDCAFitterN -{ - static constexpr double NMin = 2; - static constexpr double NMax = 4; - static constexpr double NInv = 1. / N; - static constexpr int MAXHYP = 2; - static constexpr float ZerrFactor = 5.; // factor for conversion of track covXX to dummy covZZ - using Track = o2::track::TrackParCovFwd; - using TrackAuxPar = o2::track::TrackAuxPar; - using CrossInfo = o2::track::CrossInfo; - using Vec3D = ROOT::Math::SVector; - using VecND = ROOT::Math::SVector; - using MatSym3D = ROOT::Math::SMatrix>; - using MatStd3D = ROOT::Math::SMatrix>; - using MatSymND = ROOT::Math::SMatrix>; - using MatStdND = ROOT::Math::SMatrix>; - using SMatrix55 = ROOT::Math::SMatrix>; - using TrackCoefVtx = MatStd3D; - using ArrTrack = std::array; // container for prongs (tracks) at single vertex cand. - using ArrTrackCovI = std::array; // container for inv.cov.matrices at single vertex cand. - using ArrTrCoef = std::array; // container of TrackCoefVtx coefficients at single vertex cand. - using ArrTrDer = std::array; // container of Track 1st and 2nd derivative over their Z param - using ArrTrPos = std::array; // container of Track positions - - public: - static constexpr int getNProngs() { return N; } - - FwdDCAFitterN() = default; - FwdDCAFitterN(float bz, bool useAbsDCA, bool prop2DCA) : mBz(bz), mUseAbsDCA(useAbsDCA), mPropagateToPCA(prop2DCA) - { - static_assert(N >= NMin && N <= NMax, "N prongs outside of allowed range"); - } - - //========================================================================= - ///< return PCA candidate, by default best on is provided (no check for the index validity) - const Vec3D& getPCACandidate(int cand = 0) const { return mPCA[mOrder[cand]]; } - const auto getPCACandidatePos(int cand = 0) const - { - const auto& vd = mPCA[mOrder[cand]]; - return std::array{float(vd[0]), float(vd[1]), float(vd[2])}; - } - - ///< return Chi2 at PCA candidate (no check for its validity) - float getChi2AtPCACandidate(int cand = 0) const { return mChi2[mOrder[cand]]; } - - ///< prepare copies of tracks at the V0 candidate (no check for the candidate validity) - /// must be called before getTrack(i,cand) query - bool FwdpropagateTracksToVertex(int cand = 0); - - ///< check if propagation of tracks to candidate vertex was done - bool isPropagateTracksToVertexDone(int cand = 0) const { return mTrPropDone[mOrder[cand]]; } - - ///< track param propagated to V0 candidate (no check for the candidate validity) - /// propagateTracksToVertex must be called in advance - Track& getTrack(int i, int cand = 0) - { - if (!mTrPropDone[mOrder[cand]]) { - throw std::runtime_error("propagateTracksToVertex was not called yet"); - } - return mCandTr[mOrder[cand]][i]; - } - - ///< calculate on the fly track param (no cov mat) at candidate - o2::track::TrackParFwd FwdgetTrackParamAtPCA(int i, int cand = 0) const; - - MatSym3D calcPCACovMatrix(int cand = 0) const; - - std::array calcPCACovMatrixFlat(int cand = 0) const - { - auto m = calcPCACovMatrix(cand); - return {float(m(0, 0)), float(m(1, 0)), float(m(1, 1)), float(m(2, 0)), float(m(2, 1)), float(m(2, 2))}; - } - - const Track* getOrigTrackPtr(int i) const { return mOrigTrPtr[i]; } - - ///< return number of iterations during minimization (no check for its validity) - int getNIterations(int cand = 0) const { return mNIters[mOrder[cand]]; } - void setPropagateToPCA(bool v = true) { mPropagateToPCA = v; } - void setMaxIter(int n = 60) { mMaxIter = n > 2 ? n : 2; } - void setMaxR(float r = 200.) { mMaxR2 = r * r; } - void setMaxDXIni(float d = 4.) { mMaxDXIni = d; } - void setMaxChi2(float chi2 = 999.) { mMaxChi2 = chi2; } - void setBz(float bz) { mBz = std::abs(bz) > o2::constants::math::Almost0 ? bz : 0.f; } - void setMinParamChange(float x = 1e-3) { mMinParamChange = x > 1e-4 ? x : 1.e-4; } - void setMinRelChi2Change(float r = 0.9) { mMinRelChi2Change = r > 0.1 ? r : 999.; } - void setUseAbsDCA(bool v) { mUseAbsDCA = v; } - void setMatLUT(const o2::base::MatLayerCylSet* m) - { - mMatLUT = m; - mUseMatBudget = true; - } - void setTGeoMat(bool v = true) { mTGeoFallBackAllowed = v; } - void setMaxDistance2ToMerge(float v) { mMaxDist2ToMergeSeeds = v; } - - int getNCandidates() const { return mCurHyp; } - int getMaxIter() const { return mMaxIter; } - float getMaxR() const { return std::sqrt(mMaxR2); } - float getMaxDXIni() const { return mMaxDXIni; } - float getMaxChi2() const { return mMaxChi2; } - float getMinParamChange() const { return mMinParamChange; } - float getBz() const { return mBz; } - double getK(double b) const { return std::abs(o2::constants::math::B2C * b); } - double getHz(double b) const { return std::copysign(1, b); } - - float getMaxDistance2ToMerge() const { return mMaxDist2ToMergeSeeds; } - bool getUseAbsDCA() const { return mUseAbsDCA; } - bool getPropagateToPCA() const { return mPropagateToPCA; } - - template - int process(const Tr&... args); - void print() const; - - protected: - bool FwdcalcPCACoefs(); - bool FwdcalcInverseWeight(); - void FwdcalcResidDerivatives(); - void FwdcalcResidDerivativesNoErr(); - void FwdcalcChi2Derivatives(); - void FwdcalcChi2DerivativesNoErr(); - void FwdcalcPCA(); - void FwdcalcPCANoErr(); - void FwdcalcTrackResiduals(); - void calcTrackDerivatives(); - float findZatXY(int cand = 0); - void findZatXY_mid(int cand = 0); - void findZatXY_lineApprox(int cand = 0); - void findZatXY_quad(int cand = 0); - void findZatXY_linear(int cand = 0); - double FwdcalcChi2() const; - double FwdcalcChi2NoErr() const; - bool FwdcorrectTracks(const VecND& corrZ); - bool minimizeChi2(); - bool minimizeChi2NoErr(); - bool roughDXCut() const; - bool closerToAlternative() const; - static double getAbsMax(const VecND& v); - bool propagateToVtx(o2::track::TrackParCovFwd& t, const std::array& p, const std::array& cov) const; - - ///< track param positions at V0 candidate (no check for the candidate validity) - const Vec3D& getTrackPos(int i, int cand = 0) const { return mTrPos[mOrder[cand]][i]; } - - ///< track Z-param at V0 candidate (no check for the candidate validity) - float getTrackZ(int i, int cand = 0) const { return getTrackPos(i, cand)[2]; } - - MatStd3D getTrackRotMatrix(int i) const // generate 3D matrix for track rotation to global frame - // no rotation for fwd: mat=I - { - MatStd3D mat; - mat(0, 0) = 1; - mat(1, 1) = 1; - mat(2, 2) = 1; - return mat; - } - - MatSym3D getTrackCovMatrix(int i, int cand = 0) const // generate covariance matrix of track position, adding fake Z error - { - const auto& trc = mCandTr[mOrder[cand]][i]; - MatSym3D mat; - mat(0, 0) = trc.getSigma2X(); - mat(1, 1) = trc.getSigma2Y(); - mat(1, 0) = trc.getSigmaXY(); - mat(2, 2) = trc.getSigma2Y() * ZerrFactor; - return mat; - } - - void assign(int) {} - template - void assign(int i, const T& t, const Tr&... args) - { - static_assert(std::is_convertible(), "Wrong track type"); - mOrigTrPtr[i] = &t; - assign(i + 1, args...); - } - - void clear() - { - mCurHyp = 0; - mAllowAltPreference = true; - } - - static void setTrackPos(Vec3D& pnt, const Track& tr) - { - pnt[0] = tr.getX(); - pnt[1] = tr.getY(); - pnt[2] = tr.getZ(); - } - - private: - // vectors of 1st derivatives of track local residuals over Z parameters - std::array, N> mDResidDz; - // vectors of 1nd derivatives of track local residuals over Z parameters - std::array, N> mD2ResidDz2; - VecND mDChi2Dz; // 1st derivatives of chi2 over tracks Z params - MatSymND mD2Chi2Dz2; // 2nd derivatives of chi2 over tracks Z params (symmetric matrix) - - std::array mOrigTrPtr; - std::array mTrAux; // Aux track info for each track at each cand. vertex - CrossInfo mCrossings; // info on track crossing - - std::array mTrcEInv; // errors for each track at each cand. vertex - std::array mCandTr; // tracks at each cond. vertex (Note: Errors are at seed XY point) - std::array mTrCFVT; // TrackCoefVtx for each track at each cand. vertex - std::array mTrDer; // Track derivativse - std::array mTrPos; // Track positions - std::array mTrRes; // Track residuals - std::array mPCA; // PCA for each vertex candidate - std::array mChi2 = {0}; // Chi2 at PCA candidate - std::array mNIters; // number of iterations for each seed - std::array mTrPropDone; // Flag that the tracks are fully propagated to PCA - MatSym3D mWeightInv; // inverse weight of single track, [sum{M^T E M}]^-1 in EQ.T - std::array mOrder{0}; - int mCurHyp = 0; - int mCrossIDCur = 0; - int mCrossIDAlt = -1; - bool mAllowAltPreference = true; // if the fit converges to alternative PCA seed, abandon the current one - bool mUseAbsDCA = false; // use abs. distance minimization rather than chi2 - bool mPropagateToPCA = true; // create tracks version propagated to PCA - bool mUseMatBudget = false; // include MCS effects in track propagation - bool mTGeoFallBackAllowed = true; // use TGeo for precise estimate of mat. budget - int mMaxIter = 60; // max number of iterations - float mBz = 0; // bz field, to be set by user - float mMaxR2 = 200. * 200.; // reject PCA's above this radius - float mMaxDXIni = 4.; // reject (if>0) PCA candidate if tracks DZ exceeds threshold - float mMinParamChange = 1e-5; // stop iterations if largest change of any X is smaller than this - float mMinRelChi2Change = 0.98; // stop iterations is chi2/chi2old > this - float mMaxChi2 = 100; // abs cut on chi2 or abs distance - float mMaxDist2ToMergeSeeds = 1.; // merge 2 seeds to their average if their distance^2 is below the threshold - const o2::base::MatLayerCylSet* mMatLUT = nullptr; // use to compute material budget to include MCS effects - - ClassDefNV(FwdDCAFitterN, 1); -}; - -///_________________________________________________________________________ -template -template -int FwdDCAFitterN::process(const Tr&... args) -{ - - static_assert(sizeof...(args) == N, "incorrect number of input tracks"); - assign(0, args...); - clear(); - - for (int i = 0; i < N; i++) { - mTrAux[i].set(*mOrigTrPtr[i], mBz); - } - - if (!mCrossings.set(mTrAux[0], *mOrigTrPtr[0], mTrAux[1], *mOrigTrPtr[1])) { // even for N>2 it should be enough to test just 1 loop - return 0; // no crossing - } - - if (mCrossings.nDCA == MAXHYP) { // if there are 2 candidates - auto dst2 = (mCrossings.xDCA[0] - mCrossings.xDCA[1]) * (mCrossings.xDCA[0] - mCrossings.xDCA[1]) + - (mCrossings.yDCA[0] - mCrossings.yDCA[1]) * (mCrossings.yDCA[0] - mCrossings.yDCA[1]); - - if (dst2 < mMaxDist2ToMergeSeeds) { - mCrossings.nDCA = 1; - mCrossings.xDCA[0] = 0.5 * (mCrossings.xDCA[0] + mCrossings.xDCA[1]); - mCrossings.yDCA[0] = 0.5 * (mCrossings.yDCA[0] + mCrossings.yDCA[1]); - } - } - - // check all crossings - for (int ic = 0; ic < mCrossings.nDCA; ic++) { - // check if radius is acceptable - if (mCrossings.xDCA[ic] * mCrossings.xDCA[ic] + mCrossings.yDCA[ic] * mCrossings.yDCA[ic] > mMaxR2) { - continue; - } - - mCrossIDCur = ic; - mCrossIDAlt = (mCrossings.nDCA == 2 && mAllowAltPreference) ? 1 - ic : -1; // works for max 2 crossings - mNIters[mCurHyp] = 0; - mTrPropDone[mCurHyp] = false; - mChi2[mCurHyp] = -1.; - - findZatXY_mid(mCurHyp); - - if (mUseAbsDCA ? minimizeChi2NoErr() : minimizeChi2()) { - mOrder[mCurHyp] = mCurHyp; - if (mPropagateToPCA && !FwdpropagateTracksToVertex(mCurHyp)) { - continue; - } - mCurHyp++; - } - } - - for (int i = mCurHyp; i--;) { // order in quality - for (int j = i; j--;) { - if (mChi2[mOrder[i]] < mChi2[mOrder[j]]) { - std::swap(mOrder[i], mOrder[j]); - } - } - } - - return mCurHyp; -} - -//__________________________________________________________________________ -template -bool FwdDCAFitterN::FwdcalcPCACoefs() -{ - //< calculate Ti matrices for global vertex decomposition to V = sum_{0 -bool FwdDCAFitterN::FwdcalcInverseWeight() -{ - //< calculate [sum_{0 -void FwdDCAFitterN::FwdcalcResidDerivatives() -{ - //< calculate matrix of derivatives for weighted chi2: residual i vs parameter Z of track j - MatStd3D matMT; - for (int i = N; i--;) { // residual being differentiated - // const auto& taux = mTrAux[i]; - for (int j = N; j--;) { // track over which we differentiate - const auto& matT = mTrCFVT[mCurHyp][j]; // coefficient matrix for track J - const auto& trDz = mTrDer[mCurHyp][j]; // track point derivs over track Z param - auto& dr1 = mDResidDz[i][j]; - auto& dr2 = mD2ResidDz2[i][j]; - // calculate M_i^transverse * T_j , M_i^transverse=I -> MT=T - matMT[0][0] = matT[0][0]; - matMT[0][1] = matT[0][1]; - matMT[0][2] = matT[0][2]; - matMT[1][0] = matT[1][0]; - matMT[1][1] = matT[1][1]; - matMT[1][2] = matT[1][2]; - matMT[2][0] = matT[2][0]; - matMT[2][1] = matT[2][1]; - matMT[2][2] = matT[2][2]; - - // calculate DResid_i/Dz_j = (delta_ij - M_i^tr * T_j) * DTrack_k/Dz_k - dr1[0] = -(matMT[0][0] * trDz.dxdz + matMT[0][1] * trDz.dydz + matMT[0][2]); - dr1[1] = -(matMT[1][0] * trDz.dxdz + matMT[1][1] * trDz.dydz + matMT[1][2]); - dr1[2] = -(matMT[2][0] * trDz.dxdz + matMT[2][1] * trDz.dydz + matMT[2][2]); - - // calculate D2Resid_I/(Dz_J Dz_K) = (delta_ijk - M_i^tr * T_j * delta_jk) * D2Track_k/dz_k^2 - dr2[0] = -(matMT[0][1] * trDz.d2ydz2 + matMT[0][0] * trDz.d2xdz2); - dr2[1] = -(matMT[1][1] * trDz.d2ydz2 + matMT[1][0] * trDz.d2xdz2); - dr2[2] = -(matMT[2][1] * trDz.d2ydz2 + matMT[2][0] * trDz.d2xdz2); - - if (i == j) { - dr1[0] += trDz.dxdz; - dr1[1] += trDz.dydz; - dr1[2] += 1.; - - dr2[0] += trDz.d2xdz2; - dr2[1] += trDz.d2ydz2; - } - } // track over which we differentiate - } // residual being differentiated -} - -//__________________________________________________________________________ -template -void FwdDCAFitterN::FwdcalcResidDerivativesNoErr() -{ - //< calculate matrix of derivatives for absolute distance chi2: residual i vs parameter Z of track j - constexpr double NInv1 = 1. - NInv; // profit from Rii = I/Ninv - for (int i = N; i--;) { // residual being differentiated - const auto& trDzi = mTrDer[mCurHyp][i]; // track point derivs over track Z param - auto& dr1ii = mDResidDz[i][i]; - auto& dr2ii = mD2ResidDz2[i][i]; - - dr1ii[0] = NInv1 * trDzi.dxdz; - dr1ii[1] = NInv1 * trDzi.dydz; - dr1ii[2] = NInv1; - - dr2ii[0] = NInv1 * trDzi.d2xdz2; - dr2ii[1] = NInv1 * trDzi.d2ydz2; - dr2ii[2] = 0; - - for (int j = i; j--;) { // track over which we differentiate - auto& dr1ij = mDResidDz[i][j]; - auto& dr1ji = mDResidDz[j][i]; - const auto& trDzj = mTrDer[mCurHyp][j]; // track point derivs over track Z param - - // calculate DResid_i/Dz_j = (delta_ij - R_ij) * DTrack_j/Dz_j for j -void FwdDCAFitterN::FwdcalcChi2Derivatives() -{ - //< calculate 1st and 2nd derivatives of wighted DCA (chi2) over track parameters Z - std::array, N> covIDrDz; // tempory vectors of covI_j * dres_j/dz_i - - // chi2 1st derivative - for (int i = N; i--;) { - auto& dchi1 = mDChi2Dz[i]; // DChi2/Dz_i = sum_j { res_j * covI_j * Dres_j/Dz_i } - dchi1 = 0; - for (int j = N; j--;) { - const auto& res = mTrRes[mCurHyp][j]; // vector of residuals of track j - const auto& covI = mTrcEInv[mCurHyp][j]; // inverse cov matrix of track j - const auto& dr1 = mDResidDz[j][i]; // vector of j-th residuals 1st derivative over Z param of track i - auto& cidr = covIDrDz[i][j]; // vector covI_j * dres_j/dz_i, save for 2nd derivative calculation - cidr[0] = covI.sxx * dr1[0] + covI.sxy * dr1[1]; - cidr[1] = covI.sxy * dr1[0] + covI.syy * dr1[1]; - cidr[2] = covI.szz * dr1[2]; - - dchi1 += ROOT::Math::Dot(res, cidr); - } - } - - // chi2 2nd derivative - for (int i = N; i--;) { - for (int j = i + 1; j--;) { // symmetric matrix - auto& dchi2 = mD2Chi2Dz2[i][j]; // D2Chi2/Dz_i/Dz_j = sum_k { Dres_k/Dz_j * covI_k * Dres_k/Dz_i + res_k * covI_k * D2res_k/Dz_i/Dz_j } - dchi2 = 0; - for (int k = N; k--;) { - const auto& dr1j = mDResidDz[k][j]; // vector of k-th residuals 1st derivative over Z param of track j - const auto& cidrkj = covIDrDz[i][k]; // vector covI_k * dres_k/dz_i - dchi2 += ROOT::Math::Dot(dr1j, cidrkj); - if (k == j) { - const auto& res = mTrRes[mCurHyp][k]; // vector of residuals of track k - const auto& covI = mTrcEInv[mCurHyp][k]; // inverse cov matrix of track k - const auto& dr2ij = mD2ResidDz2[k][j]; // vector of k-th residuals 2nd derivative over Z params of track j - dchi2 += res[0] * (covI.sxx * dr2ij[0] + covI.sxy * dr2ij[1]) + res[1] * (covI.sxy * dr2ij[0] + covI.syy * dr2ij[1]) + res[2] * covI.szz * dr2ij[2]; - } - } - } - } -} - -//__________________________________________________________________________ -template -void FwdDCAFitterN::FwdcalcChi2DerivativesNoErr() -{ - //< calculate 1st and 2nd derivatives of abs DCA (chi2) over track parameters Z - for (int i = N; i--;) { - auto& dchi1 = mDChi2Dz[i]; // DChi2/Dz_i = sum_j { res_j * Dres_j/Dz_i } - dchi1 = 0; // chi2 1st derivative - for (int j = N; j--;) { - const auto& res = mTrRes[mCurHyp][j]; // vector of residuals of track j - const auto& dr1 = mDResidDz[j][i]; // vector of j-th residuals 1st derivative over Z param of track i - dchi1 += ROOT::Math::Dot(res, dr1); - if (i >= j) { // symmetrix matrix - // chi2 2nd derivative - auto& dchi2 = mD2Chi2Dz2[i][j]; // D2Chi2/Dz_i/Dz_j = sum_k { Dres_k/Dz_j * covI_k * Dres_k/Dz_i + res_k * covI_k * D2res_k/Dz_i/Dz_j } - dchi2 = ROOT::Math::Dot(mTrRes[mCurHyp][i], mD2ResidDz2[i][j]); - for (int k = N; k--;) { - dchi2 += ROOT::Math::Dot(mDResidDz[k][i], mDResidDz[k][j]); - } - } - } - } -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::FwdcalcPCA() -{ - // calculate point of closest approach for N prongs - // calculating V = sum (Ti*Pi) - mPCA[mCurHyp] = mTrCFVT[mCurHyp][N - 1] * mTrPos[mCurHyp][N - 1]; - for (int i = N - 1; i--;) { - mPCA[mCurHyp] += mTrCFVT[mCurHyp][i] * mTrPos[mCurHyp][i]; - } -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::FwdcalcPCANoErr() -{ - // calculate point of closest approach for N prongs w/o errors - auto& pca = mPCA[mCurHyp]; - - pca[0] = mTrPos[mCurHyp][N - 1][0]; - pca[1] = mTrPos[mCurHyp][N - 1][1]; - pca[2] = mTrPos[mCurHyp][N - 1][2]; - - for (int i = N - 1; i--;) { - pca[0] += mTrPos[mCurHyp][i][0]; - pca[1] += mTrPos[mCurHyp][i][1]; - pca[2] += mTrPos[mCurHyp][i][2]; - } - pca[0] *= NInv; - pca[1] *= NInv; - pca[2] *= NInv; -} - -//___________________________________________________________________ -template -ROOT::Math::SMatrix> FwdDCAFitterN::calcPCACovMatrix(int cand) const -{ - // calculate covariance matrix for the point of closest approach - MatSym3D covm; - for (int i = N; i--;) { - covm += ROOT::Math::Similarity(mUseAbsDCA ? getTrackRotMatrix(i) : mTrCFVT[mOrder[cand]][i], getTrackCovMatrix(i, cand)); - } - return covm; -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::FwdcalcTrackResiduals() -{ - // calculate residuals, res = Pi - V - Vec3D vtxLoc; - for (int i = N; i--;) { - mTrRes[mCurHyp][i] = mTrPos[mCurHyp][i]; - vtxLoc = mPCA[mCurHyp]; - mTrRes[mCurHyp][i] -= vtxLoc; - } -} - -//___________________________________________________________________ -template -inline void FwdDCAFitterN::calcTrackDerivatives() -{ - // calculate track derivatives over Z param - for (int i = N; i--;) { - mTrDer[mCurHyp][i].set(mCandTr[mCurHyp][i], mBz); - } -} - -//___________________________________________________________________ -template -inline double FwdDCAFitterN::FwdcalcChi2() const -{ - // calculate current chi2 - double chi2 = 0; - for (int i = N; i--;) { - const auto& res = mTrRes[mCurHyp][i]; - const auto& covI = mTrcEInv[mCurHyp][i]; - chi2 += res[0] * res[0] * covI.sxx + res[1] * res[1] * covI.syy + res[2] * res[2] * covI.szz + 2. * res[0] * res[1] * covI.sxy; - } - return chi2; -} - -//___________________________________________________________________ -template -inline double FwdDCAFitterN::FwdcalcChi2NoErr() const -{ - // calculate current chi2 of abs. distance minimization - double chi2 = 0; - for (int i = N; i--;) { - const auto& res = mTrRes[mCurHyp][i]; - chi2 += res[0] * res[0] + res[1] * res[1] + res[2] * res[2]; - } - return chi2; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::FwdcorrectTracks(const VecND& corrZ) -{ - // propagate tracks to updated Z - for (int i = N; i--;) { - const auto& trDer = mTrDer[mCurHyp][i]; - auto dz2h = 0.5 * corrZ[i] * corrZ[i]; - mTrPos[mCurHyp][i][0] -= trDer.dxdz * corrZ[i] - dz2h * trDer.d2xdz2; - mTrPos[mCurHyp][i][1] -= trDer.dydz * corrZ[i] - dz2h * trDer.d2ydz2; - mTrPos[mCurHyp][i][2] -= corrZ[i]; - } - - return true; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::FwdpropagateTracksToVertex(int icand) -{ - // propagate on z axis to vertex - int ord = mOrder[icand]; - if (mTrPropDone[ord]) { - return true; - } - const Vec3D& pca = mPCA[ord]; - std::array covMatrixPCA = calcPCACovMatrixFlat(ord); - std::array cov = {covMatrixPCA[0], covMatrixPCA[2]}; - for (int i = N; i--;) { - mCandTr[ord][i] = *mOrigTrPtr[i]; // fetch the track again, as mCandTr might have been propagated w/o errors - auto& trc = mCandTr[ord][i]; - const std::array p = {(float)pca[0], (float)pca[1], (float)pca[2]}; - if (!propagateToVtx(trc, p, cov)) { - return false; - } - } - - mTrPropDone[ord] = true; - return true; -} - -//___________________________________________________________________ -template -float FwdDCAFitterN::findZatXY(int mCurHyp) // Between 2 tracks -{ - - double step = 0.001; // initial step - double startPoint = 20.; // first MFT disk - - double z[2] = {startPoint, startPoint}; - double newX[2], newY[2]; - - double X = mPCA[mCurHyp][0]; // X seed - double Y = mPCA[mCurHyp][1]; // Y seed - - mCandTr[mCurHyp][0] = *mOrigTrPtr[0]; - mCandTr[mCurHyp][1] = *mOrigTrPtr[1]; - - double dstXY[2][3] = {{999., 999., 999.}, {999., 999., 999.}}; - - double Z[2]; - double finalZ[2]; - - double newDstXY; - - for (int i = 0; i < 2; i++) { - - while (z[i] > -10) { - - mCandTr[mCurHyp][i].propagateParamToZquadratic(z[i], mBz); - newX[i] = mCandTr[mCurHyp][i].getX(); - newY[i] = mCandTr[mCurHyp][i].getY(); - - newDstXY = std::sqrt((newX[i] - X) * (newX[i] - X) + - (newY[i] - Y) * (newY[i] - Y)); - - // Update points - dstXY[i][0] = dstXY[i][1]; - dstXY[i][1] = dstXY[i][2]; - dstXY[i][2] = newDstXY; - - if (dstXY[i][2] > dstXY[i][1] && dstXY[i][1] < dstXY[i][0]) { - finalZ[i] = z[i] + step; - break; - } - - z[i] -= step; - } - } - - float rez = 0.5 * (finalZ[0] + finalZ[1]); - return rez; -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::findZatXY_mid(int mCurHyp) -{ - // look into dXY of T0 - T1 between 2 points(0,40cm); the one with the highest dXY is moved to mid - - double startPoint = -40.; - double endPoint = 50.; - double midPoint = 0.5 * (startPoint + endPoint); - - double z[2][2] = {{startPoint, endPoint}, {startPoint, endPoint}}; // z for tracks 0/1 on starting poing and endpoint - - double DeltaZ = std::abs(endPoint - startPoint); - - double newX[2][2]; - double newY[2][2]; - - double epsilon = 0.0001; - - double X = mPCA[mCurHyp][0]; // X seed - double Y = mPCA[mCurHyp][1]; // Y seed - - mCandTr[mCurHyp][0] = *mOrigTrPtr[0]; - mCandTr[mCurHyp][1] = *mOrigTrPtr[1]; - - double finalZ; - - double dstXY[2]; // 0 -> distance btwn both tracks at startPoint - - while (DeltaZ > epsilon) { - - midPoint = 0.5 * (startPoint + endPoint); - - for (int i = 0; i < 2; i++) { - mCandTr[mCurHyp][i].propagateParamToZquadratic(startPoint, mBz); - newX[i][0] = mCandTr[mCurHyp][i].getX(); - newY[i][0] = mCandTr[mCurHyp][i].getY(); - - mCandTr[mCurHyp][i].propagateParamToZquadratic(endPoint, mBz); - newX[i][1] = mCandTr[mCurHyp][i].getX(); - newY[i][1] = mCandTr[mCurHyp][i].getY(); - } - - dstXY[0] = (newX[0][0] - newX[1][0]) * (newX[0][0] - newX[1][0]) + - (newY[0][0] - newY[1][0]) * (newY[0][0] - newY[1][0]); - - dstXY[1] = (newX[0][1] - newX[1][1]) * (newX[0][1] - newX[1][1]) + - (newY[0][1] - newY[1][1]) * (newY[0][1] - newY[1][1]); - - DeltaZ = std::abs(endPoint - startPoint); - - if (DeltaZ < epsilon) { - finalZ = 0.5 * (startPoint + endPoint); - break; - } - - // chose new start and end Point according to the smallest D_XY - if (dstXY[1] > dstXY[0]) { - endPoint = midPoint; - } else { - startPoint = midPoint; - } - } - - mPCA[mCurHyp][2] = finalZ; -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::findZatXY_lineApprox(int mCurHyp) -{ - // approx method: z=(b-b')/(a'-a) -> tracks to lines with y0,1=az0,1+b for each track (in YZ and XZ plane) - - double startPoint = 1.; - double endPoint = 50.; // first disk - - double X = mPCA[mCurHyp][0]; // X seed - double Y = mPCA[mCurHyp][1]; // Y seed - - mCandTr[mCurHyp][0] = *mOrigTrPtr[0]; - mCandTr[mCurHyp][1] = *mOrigTrPtr[1]; - - double y[2][2]; // Y00: y track 0 at point 0; Y01: y track 0 at point 1 - double z[2][2]; - double x[2][2]; - - double aYZ[2]; - double bYZ[2]; - - double aXZ[2]; - double bXZ[2]; - - double finalZ; - - // find points of the tracks = 2 straight lines - for (int i = 0; i < 2; i++) { - - mCandTr[mCurHyp][i].propagateToZquadratic(startPoint, mBz); - // mCandTr[mCurHyp][i].propagateToZlinear(startPoint); - z[i][0] = startPoint; - y[i][0] = mCandTr[mCurHyp][i].getY(); - x[i][0] = mCandTr[mCurHyp][i].getX(); - - mCandTr[mCurHyp][i].propagateToZquadratic(endPoint, mBz); - // mCandTr[mCurHyp][i].propagateToZlinear(endPoint); - z[i][1] = endPoint; - y[i][1] = mCandTr[mCurHyp][i].getY(); - x[i][1] = mCandTr[mCurHyp][i].getX(); - - bYZ[i] = (y[i][1] - y[i][0] * z[i][1] / z[i][0]) / (1 - z[i][1] / z[i][0]); - aYZ[i] = (y[i][0] - bYZ[i]) / z[i][0]; - - bXZ[i] = (x[i][1] - x[i][0] * z[i][1] / z[i][0]) / (1 - z[i][1] / z[i][0]); - aXZ[i] = (x[i][0] - bXZ[i]) / z[i][0]; - } - - // z seed: equ. for intersection of these lines - finalZ = 0.5 * ((bYZ[0] - bYZ[1]) / (aYZ[1] - aYZ[0]) + (bXZ[0] - bXZ[1]) / (aXZ[1] - aXZ[0])); - - mPCA[mCurHyp][2] = finalZ; -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::findZatXY_quad(int mCurHyp) -{ - double startPoint = 0.; - double endPoint = 40.; // first disk - - double X = mPCA[mCurHyp][0]; // X seed - double Y = mPCA[mCurHyp][1]; // Y seed - - mCandTr[mCurHyp][0] = *mOrigTrPtr[0]; - mCandTr[mCurHyp][1] = *mOrigTrPtr[1]; - - double x[2]; - double y[2]; - double sinPhi0[2]; - double cosPhi0[2]; - double tanL0[2]; - double qpt0[2]; - - double k[2]; // B2C *abs(mBz) - double Hz[2]; // mBz/abs(mBz) - - double Ax[2], Bx[2], Cx[2]; - double Ay[2], By[2], Cy[2]; - - double deltaX[2], deltaY[2]; - - bool posX[2], nulX[2], negX[2]; - double z1X[2], z2X[2], z12X[2]; - - bool posY[2], nulY[2], negY[2]; - double z1Y[2], z2Y[2], z12Y[2]; - - double finalZ[2]; - - // find all variables for 2 tracks at z0 = startPoint - // set A, B, C variables for x/y equation for 2 tracks - // calculate Deltax/y for both and roots - - for (int i = 0; i < 2; i++) { - mCandTr[mCurHyp][i].propagateToZquadratic(startPoint, mBz); - x[i] = mCandTr[mCurHyp][i].getX(); - y[i] = mCandTr[mCurHyp][i].getY(); - sinPhi0[i] = mCandTr[mCurHyp][i].getSnp(); - cosPhi0[i] = std::sqrt((1. - sinPhi0[i]) * (1. + sinPhi0[i])); - tanL0[i] = mCandTr[mCurHyp][i].getTanl(); - qpt0[i] = mCandTr[mCurHyp][i].getInvQPt(); - k[i] = getK(mBz); - Hz[i] = getHz(mBz); - - Ax[i] = qpt0[i] * Hz[i] * k[i] * sinPhi0[i] / (2 * tanL0[i] * tanL0[i]); - Bx[i] = cosPhi0[i] / tanL0[i]; - Cx[i] = x[i] - X; - - Ay[i] = -qpt0[i] * Hz[i] * k[i] * cosPhi0[i] / (2 * tanL0[i] * tanL0[i]); - By[i] = sinPhi0[i] / tanL0[i]; - Cy[i] = y[i] - Y; // - - deltaX[i] = Bx[i] * Bx[i] - 4 * Ax[i] * Cx[i]; - deltaY[i] = By[i] * By[i] - 4 * Ay[i] * Cy[i]; - - if (deltaX[i] > 0) { - posX[i] = true; - z1X[i] = (-Bx[i] - std::sqrt(deltaX[i])) / (2 * Ax[i]); - z2X[i] = (-Bx[i] + std::sqrt(deltaX[i])) / (2 * Ax[i]); - } else if (deltaX[i] == 0) { - nulX[i] = true; - z12X[i] = -Bx[i] / (2 * Ax[i]); - } else { - negX[i] = true; - z12X[i] = 0; - } // discard - - if (deltaY[i] > 0) { - posY[i] = true; - z1Y[i] = (-By[i] - std::sqrt(deltaY[i])) / (2 * Ay[i]); - z2Y[i] = (-By[i] + std::sqrt(deltaY[i])) / (2 * Ay[i]); - } else if (deltaX[i] == 0) { - nulY[i] = true; - z12Y[i] = -By[i] / (2 * Ay[i]); - } else { - negY[i] = true; - z12Y[i] = 0; - } - - // find the z located in an acceptable interval - if (posX[i]) { - if (z1X[i] < endPoint && z1X[i] > startPoint) { - z12X[i] = z1X[i]; - } else { - z12X[i] = z2X[i]; - } - } - - if (posY[i]) { - if (z1Y[i] < endPoint && z1Y[i] > startPoint) { - z12Y[i] = z1Y[i]; - } else { - z12Y[i] = z2Y[i]; - } - } - - finalZ[i] = 0.5 * (z12X[i] + z12Y[i]); - } - - mPCA[mCurHyp][2] = 0.5 * (finalZ[0] + finalZ[1]); -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::findZatXY_linear(int mCurHyp) -{ - - double startPoint = 0.; - - double X = mPCA[mCurHyp][0]; // X seed - double Y = mPCA[mCurHyp][1]; // Y seed - - mCandTr[mCurHyp][0] = *mOrigTrPtr[0]; - mCandTr[mCurHyp][1] = *mOrigTrPtr[1]; - - double x[2]; - double y[2]; - double sinPhi0[2]; - double cosPhi0[2]; - double tanL0[2]; - - double Ax[2], Bx[2]; - double Ay[2], By[2]; - - double z12X[2]; - double z12Y[2]; - - double finalZ[2]; - - // find all variables for 2 tracks at z0 = startPoint - // set A, B variables for x/y equation for 2 tracks - // calculate root - - for (int i = 0; i < 2; i++) { - mCandTr[mCurHyp][i].propagateToZlinear(startPoint); - x[i] = mCandTr[mCurHyp][i].getX(); - y[i] = mCandTr[mCurHyp][i].getY(); - sinPhi0[i] = mCandTr[mCurHyp][i].getSnp(); - cosPhi0[i] = std::sqrt((1. - sinPhi0[i]) * (1. + sinPhi0[i])); - tanL0[i] = mCandTr[mCurHyp][i].getTanl(); - - Ax[i] = cosPhi0[i] / tanL0[i]; - Bx[i] = x[i] - X; - - Ay[i] = sinPhi0[i] / tanL0[i]; - By[i] = y[i] - Y; - - z12X[i] = -Bx[i] / Ax[i]; - z12Y[i] = -By[i] / Ay[i]; - - finalZ[i] = 0.5 * (z12X[i] + z12Y[i]); - } - - mPCA[mCurHyp][2] = 0.5 * (finalZ[0] + finalZ[1]); -} - -//___________________________________________________________________ -template -inline o2::track::TrackParFwd FwdDCAFitterN::FwdgetTrackParamAtPCA(int i, int icand) const -{ - // propagate tracks param only to current vertex (if not already done) - int ord = mOrder[icand]; - o2::track::TrackParFwd trc(mCandTr[ord][i]); - if (!mTrPropDone[ord]) { - auto z = mPCA[ord][2]; - trc.propagateParamToZquadratic(z, mBz); - } - - return {trc}; -} - -//___________________________________________________________________ -template -inline double FwdDCAFitterN::getAbsMax(const VecND& v) -{ - double mx = -1; - for (int i = N; i--;) { - auto vai = std::abs(v[i]); - if (mx < vai) { - mx = vai; - } - } - return mx; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::minimizeChi2() -{ - // find best chi2 (weighted DCA) of N tracks in the vicinity of the seed PCA - double x[2], y[2]; - double sumX = 0.; - double sumY = 0.; - - for (int i = N; i--;) { - mCandTr[mCurHyp][i] = *mOrigTrPtr[i]; - auto z = mPCA[mCurHyp][2]; - - mCandTr[mCurHyp][i].propagateToZquadratic(z, mBz); - - x[i] = mCandTr[mCurHyp][i].getX(); - y[i] = mCandTr[mCurHyp][i].getY(); - - setTrackPos(mTrPos[mCurHyp][i], mCandTr[mCurHyp][i]); // prepare positions - mTrcEInv[mCurHyp][i].set(mCandTr[mCurHyp][i], ZerrFactor); // prepare inverse cov.matrices at starting point - - sumX = sumX + x[i]; - sumY = sumY + y[i]; - } - - mPCA[mCurHyp][0] = sumX / N; - mPCA[mCurHyp][1] = sumY / N; - - if (mMaxDXIni > 0 && !roughDXCut()) { // apply rough cut on tracks X difference - return false; - } - - if (!FwdcalcPCACoefs()) { // prepare tracks contribution matrices to the global PCA - return false; - } - FwdcalcPCA(); // current PCA - FwdcalcTrackResiduals(); // current track residuals - float chi2Upd, chi2 = FwdcalcChi2(); - do { - calcTrackDerivatives(); // current track derivatives (1st and 2nd) - FwdcalcResidDerivatives(); // current residals derivatives (1st and 2nd) - FwdcalcChi2Derivatives(); // current chi2 derivatives (1st and 2nd) to proceed for dz calculation - - // do Newton-Rapson iteration with corrections = - dchi2/d{x0..xN} * [ d^2chi2/d{x0..xN}^2 ]^-1 - if (!mD2Chi2Dz2.Invert()) { - return false; - } - - VecND dz = mD2Chi2Dz2 * mDChi2Dz; - - if (!FwdcorrectTracks(dz)) { // calculate new Pi (mTrPos) following Newton-Rapson iteration - return false; - } - - FwdcalcPCA(); // updated mPCA (new V coordinates with new mTrPos (Pi)) - if (mCrossIDAlt >= 0 && closerToAlternative()) { - mAllowAltPreference = false; - return false; - } - - FwdcalcTrackResiduals(); // updated residuals - chi2Upd = FwdcalcChi2(); // updated chi2 - - if (getAbsMax(dz) < mMinParamChange || chi2Upd > chi2 * mMinRelChi2Change) { - chi2 = chi2Upd; - break; // converged - } - - chi2 = chi2Upd; - } while (++mNIters[mCurHyp] < mMaxIter); - - mChi2[mCurHyp] = chi2 * NInv; - return mChi2[mCurHyp] < mMaxChi2; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::minimizeChi2NoErr() -{ - // find best chi2 (absolute DCA) of N tracks in the vicinity of the PCA seed - double x[2], y[2]; - double sumX = 0.; - double sumY = 0.; - - for (int i = N; i--;) { - - mCandTr[mCurHyp][i] = *mOrigTrPtr[i]; - - auto z = mPCA[mCurHyp][2]; - mCandTr[mCurHyp][i].propagateParamToZquadratic(z, mBz); - - x[i] = mCandTr[mCurHyp][i].getX(); - y[i] = mCandTr[mCurHyp][i].getY(); - - mPCA[mCurHyp][2] = z; - - setTrackPos(mTrPos[mCurHyp][i], mCandTr[mCurHyp][i]); // prepare positions - - sumX = sumX + x[i]; - sumY = sumY + y[i]; - } - - mPCA[mCurHyp][0] = sumX / N; - mPCA[mCurHyp][1] = sumY / N; - - if (mMaxDXIni > 0 && !roughDXCut()) { // apply rough cut on tracks Z difference - return false; - } - - FwdcalcPCANoErr(); // current PCA - FwdcalcTrackResiduals(); // current track residuals - float chi2Upd, chi2 = FwdcalcChi2NoErr(); - do { - calcTrackDerivatives(); // current track derivatives (1st and 2nd) - FwdcalcResidDerivativesNoErr(); // current residals derivatives (1st and 2nd) - FwdcalcChi2DerivativesNoErr(); // current chi2 derivatives (1st and 2nd) - - // do Newton-Rapson iteration with corrections = - dchi2/d{x0..xN} * [ d^2chi2/d{x0..xN}^2 ]^-1 - if (!mD2Chi2Dz2.Invert()) { - return false; - } - VecND dz = mD2Chi2Dz2 * mDChi2Dz; - - if (!FwdcorrectTracks(dz)) { - return false; - } - FwdcalcPCANoErr(); // updated PCA - if (mCrossIDAlt >= 0 && closerToAlternative()) { - mAllowAltPreference = false; - return false; - } - FwdcalcTrackResiduals(); // updated residuals - chi2Upd = FwdcalcChi2NoErr(); // updated chi2 - if (getAbsMax(dz) < mMinParamChange || chi2Upd > chi2 * mMinRelChi2Change) { - chi2 = chi2Upd; - break; // converged - } - chi2 = chi2Upd; - } while (++mNIters[mCurHyp] < mMaxIter); - // - mChi2[mCurHyp] = chi2 * NInv; - return mChi2[mCurHyp] < mMaxChi2; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::roughDXCut() const -{ - // apply rough cut on DX between the tracks in the seed point - - bool accept = true; - for (int i = N; accept && i--;) { - for (int j = i; j--;) { - if (std::abs(mCandTr[mCurHyp][i].getX() - mCandTr[mCurHyp][j].getX()) > mMaxDXIni) { - accept = false; - break; - } - } - } - return accept; -} - -//___________________________________________________________________ -template -bool FwdDCAFitterN::closerToAlternative() const -{ - // check if the point current PCA point is closer to the seeding XY point being tested or to alternative see (if any) - auto dxCur = mPCA[mCurHyp][0] - mCrossings.xDCA[mCrossIDCur], dyCur = mPCA[mCurHyp][1] - mCrossings.yDCA[mCrossIDCur]; - auto dxAlt = mPCA[mCurHyp][0] - mCrossings.xDCA[mCrossIDAlt], dyAlt = mPCA[mCurHyp][1] - mCrossings.yDCA[mCrossIDAlt]; - return dxCur * dxCur + dyCur * dyCur > dxAlt * dxAlt + dyAlt * dyAlt; -} - -//___________________________________________________________________ -template -void FwdDCAFitterN::print() const -{ - LOG(info) << N << "-prong vertex fitter in " << (mUseAbsDCA ? "abs." : "weighted") << " distance minimization mode"; - LOG(info) << "Bz: " << mBz << " MaxIter: " << mMaxIter << " MaxChi2: " << mMaxChi2; - LOG(info) << "Stopping condition: Max.param change < " << mMinParamChange << " Rel.Chi2 change > " << mMinRelChi2Change; - LOG(info) << "Discard candidates for : Rvtx > " << getMaxR() << " DZ between tracks > " << mMaxDXIni; -} -//___________________________________________________________________ -template -inline bool FwdDCAFitterN::propagateToVtx(o2::track::TrackParCovFwd& t, const std::array& p, const std::array& cov) const -{ - // propagate track to vertex including MCS effects if material budget included, simple propagation to Z otherwise - float x2x0 = 0; - if (mUseMatBudget) { - auto mb = mMatLUT->getMatBudget(t.getX(), t.getY(), t.getZ(), p[0], p[1], p[2]); - x2x0 = (float)mb.meanX2X0; - return t.propagateToVtxhelixWithMCS(p[2], {p[0], p[1]}, cov, mBz, x2x0); - } else if (mTGeoFallBackAllowed) { - auto geoMan = o2::base::GeometryManager::meanMaterialBudget(t.getX(), t.getY(), t.getZ(), p[0], p[1], p[2]); - x2x0 = (float)geoMan.meanX2X0; - return t.propagateToVtxhelixWithMCS(p[2], {p[0], p[1]}, cov, mBz, x2x0); - } else { - t.propagateToZhelix(p[2], mBz); - return true; - } -} - -using FwdDCAFitter2 = FwdDCAFitterN<2, o2::track::TrackParCovFwd>; -using FwdDCAFitter3 = FwdDCAFitterN<3, o2::track::TrackParCovFwd>; - -} // namespace vertexing -} // namespace o2 -#endif // _ALICEO2_DCA_FWDFITTERN_ diff --git a/Detectors/Vertexing/include/DetectorsVertexing/HelixHelper.h b/Detectors/Vertexing/include/DetectorsVertexing/HelixHelper.h deleted file mode 100644 index 72066250f1053..0000000000000 --- a/Detectors/Vertexing/include/DetectorsVertexing/HelixHelper.h +++ /dev/null @@ -1,282 +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 HelixHelper.h -/// \brief Helper classes for helical tracks manipulations -/// \author ruben.shahoyan@cern.ch - -#ifndef _ALICEO2_HELIX_HELPER_ -#define _ALICEO2_HELIX_HELPER_ - -#include "CommonConstants/MathConstants.h" -#include "MathUtils/Utils.h" -#include "MathUtils/Primitive2D.h" - -namespace o2 -{ -namespace track -{ - -///__________________________________________________________________________ -//< precalculated track radius, center, alpha sin,cos and their combinations -struct TrackAuxPar : public o2::math_utils::CircleXYf_t { - float c, s, cc, ss, cs; // cos ans sin of track alpha and their products - - TrackAuxPar() = default; - - template - TrackAuxPar(const T& trc, float bz) - { - set(trc, bz); - } - float cosDif(const TrackAuxPar& t) const { return c * t.c + s * t.s; } // cos(alpha_this - alha_t) - float sinDif(const TrackAuxPar& t) const { return s * t.c - c * t.s; } // sin(alpha_this - alha_t) - - template - void set(const T& trc, float bz) - { - trc.getCircleParams(bz, *this, s, c); - cc = c * c; - ss = s * s; - cs = c * s; - } - ClassDefNV(TrackAuxPar, 1); -}; - -//__________________________________________________________ -//< crossing coordinates of 2 circles -struct CrossInfo { - static constexpr float MaxDistXYDef = 10.; - float xDCA[2] = {}; - float yDCA[2] = {}; - int nDCA = 0; - - int circlesCrossInfo(const TrackAuxPar& trax0, const TrackAuxPar& trax1, float maxDistXY = MaxDistXYDef) - { - const auto& trcA = trax0.rC > trax1.rC ? trax0 : trax1; // designate the largest circle as A - const auto& trcB = trax0.rC > trax1.rC ? trax1 : trax0; - float xDist = trcB.xC - trcA.xC, yDist = trcB.yC - trcA.yC; - float dist2 = xDist * xDist + yDist * yDist, dist = std::sqrt(dist2), rsum = trcA.rC + trcB.rC; - if (std::abs(dist) < 1e-12) { - return nDCA; // circles are concentric? - } - if (dist > rsum) { // circles don't touch, chose a point in between - // the parametric equation of lines connecting the centers is - // x = x0 + t/dist * (x1-x0), y = y0 + t/dist * (y1-y0) - if (dist - rsum > maxDistXY) { // too large distance - return nDCA; - } - notTouchingXY(dist, xDist, yDist, trcA, trcB.rC); - } else if (dist + trcB.rC < trcA.rC) { // the small circle is nestled into large one w/o touching - // select the point of closest approach of 2 circles - notTouchingXY(dist, xDist, yDist, trcA, -trcB.rC); - } else { // 2 intersection points - // to simplify calculations, we move to new frame x->x+Xc0, y->y+Yc0, so that - // the 1st one is centered in origin - if (std::abs(xDist) < std::abs(yDist)) { - float a = (trcA.rC * trcA.rC - trcB.rC * trcB.rC + dist2) / (2. * yDist), b = -xDist / yDist, ab = a * b, bb = b * b; - float det = ab * ab - (1. + bb) * (a * a - trcA.rC * trcA.rC); - if (det > 0.) { - det = std::sqrt(det); - xDCA[0] = (-ab + det) / (1. + b * b); - yDCA[0] = a + b * xDCA[0] + trcA.yC; - xDCA[0] += trcA.xC; - xDCA[1] = (-ab - det) / (1. + b * b); - yDCA[1] = a + b * xDCA[1] + trcA.yC; - xDCA[1] += trcA.xC; - nDCA = 2; - } else { // due to the finite precision the det<=0, i.e. the circles are barely touching, fall back to this special case - notTouchingXY(dist, xDist, yDist, trcA, trcB.rC); - } - } else { - float a = (trcA.rC * trcA.rC - trcB.rC * trcB.rC + dist2) / (2. * xDist), b = -yDist / xDist, ab = a * b, bb = b * b; - float det = ab * ab - (1. + bb) * (a * a - trcA.rC * trcA.rC); - if (det > 0.) { - det = std::sqrt(det); - yDCA[0] = (-ab + det) / (1. + bb); - xDCA[0] = a + b * yDCA[0] + trcA.xC; - yDCA[0] += trcA.yC; - yDCA[1] = (-ab - det) / (1. + bb); - xDCA[1] = a + b * yDCA[1] + trcA.xC; - yDCA[1] += trcA.yC; - nDCA = 2; - } else { // due to the finite precision the det<=0, i.e. the circles are barely touching, fall back to this special case - notTouchingXY(dist, xDist, yDist, trcA, trcB.rC); - } - } - } - return nDCA; - } - - void notTouchingXY(float dist, float xDist, float yDist, const TrackAuxPar& trcA, float rBSign) - { - // fast method to calculate DCA between 2 circles, assuming that they don't touch each outer: - // the parametric equation of lines connecting the centers is x = xA + t/dist * xDist, y = yA + t/dist * yDist - // with xA,yY being the center of the circle A ( = trcA.xC, trcA.yC ), xDist = trcB.xC = trcA.xC ... - // There are 2 special cases: - // (a) small circle is inside the large one: provide rBSign as -trcB.rC - // (b) circle are side by side: provide rBSign as trcB.rC - nDCA = 1; - auto t2d = (dist + trcA.rC - rBSign) / dist; - xDCA[0] = trcA.xC + 0.5 * (xDist * t2d); - yDCA[0] = trcA.yC + 0.5 * (yDist * t2d); - } - - template - int linesCrossInfo(const TrackAuxPar& trax0, const T& tr0, - const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) - { - /// closest approach of 2 straight lines - /// TrackParam propagation can be parameterized in lab in a form - /// xLab(t) = (x*cosAlp - y*sinAlp) + t*(cosAlp - sinAlp* snp/csp) = xLab0 + t*(cosAlp - sinAlp* snp/csp) - /// yLab(t) = (x*sinAlp + y*cosAlp) + t*(sinAlp + cosAlp* snp/csp) = yLab0 + t*(sinAlp + cosAlp* snp/csp) - /// zLab(t) = z + t * tgl / csp = zLab0 + t * tgl / csp - /// where t is the x-step in the track alpha-frame, xLab,yLab,zLab are reference track coordinates in lab - /// frame (filled by TrackAuxPar for straight line tracks). - /// - /// Therefore, for the parametric track equation in lab 3D we have (wrt tracking-X increment t) - /// xL(t) = xL + t Kx; Kx = (cosAlp - sinAlp* snp/csp) - /// yL(t) = yL + t Ky; Ky = (sinAlp + cosAlp* snp/csp) - /// zL(t) = zL + t Kz; Kz = tgl / csp - /// Note that Kx^2 + Ky^2 + Kz^2 = (1+tgl^2) / csp^2 - - float dx = trax1.xC - trax0.xC; // for straight line TrackAuxPar stores lab coordinates at referene point!!! - float dy = trax1.yC - trax0.yC; // - float dz = tr1.getZ() - tr0.getZ(); - auto csp0i2 = 1. / tr0.getCsp2(); // 1 / csp^2 - auto csp0i = std::sqrt(csp0i2); - auto tgp0 = tr0.getSnp() * csp0i; - float kx0 = trax0.c - trax0.s * tgp0; - float ky0 = trax0.s + trax0.c * tgp0; - float kz0 = tr0.getTgl() * csp0i; - auto csp1i2 = 1. / tr1.getCsp2(); // 1 / csp^2 - auto csp1i = std::sqrt(csp1i2); - auto tgp1 = tr1.getSnp() * std::sqrt(csp1i2); - float kx1 = trax1.c - trax1.s * tgp1; - float ky1 = trax1.s + trax1.c * tgp1; - float kz1 = tr1.getTgl() * csp1i; - /// Minimize |vecL1 - vecL0|^2 wrt t0 and t1: point of closest approach - /// Leads to system - /// A Dx = B with Dx = {dx0, dx1} - /// with A = - /// | kx0^2+ky0^2+kz0^2 -(kx0*kx1+ky0*ky1+kz0*kz1) | = (1+tgl0^2) / csp0^2 .... - /// | -(kx0*kx1+ky0*ky1+kz0*kz1) kx0^2+ky0^2+kz0^2 | ..... (1+tgl1^2) / csp1^2 - /// and B = {(dx Kx0 + dy Ky0 + dz Kz0), -(dx Kx1 + dy Ky1 + dz Kz1) } - /// - float a00 = (1.f + tr0.getTgl() * tr0.getTgl()) * csp0i2, a11 = (1.f + tr1.getTgl() * tr1.getTgl()) * csp1i2, a01 = -(kx0 * kx1 + ky0 * ky1 + kz0 * kz1); - float b0 = dx * kx0 + dy * ky0 + dz * kz0, b1 = -(dx * kx1 + dy * ky1 + dz * kz1); - float det = a00 * a11 - a01 * a01, det0 = b0 * a11 - b1 * a01, det1 = a00 * b1 - a01 * b0; - if (std::abs(det) > o2::constants::math::Almost0) { - auto detI = 1. / det; - auto t0 = det0 * detI; - auto t1 = det1 * detI; - float addx0 = kx0 * t0, addy0 = ky0 * t0, addx1 = kx1 * t1, addy1 = ky1 * t1; - dx += addx1 - addx0; // recalculate XY distance at DCA - dy += addy1 - addy0; - if (dx * dx + dy * dy > maxDistXY * maxDistXY) { - return nDCA; - } - xDCA[0] = (trax0.xC + addx0 + trax1.xC + addx1) * 0.5; - yDCA[0] = (trax0.yC + addy0 + trax1.yC + addy1) * 0.5; - nDCA = 1; - } - return nDCA; - } - - template - int circleLineCrossInfo(const TrackAuxPar& trax0, const T& tr0, - const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) - { - /// closest approach of line and circle - /// TrackParam propagation can be parameterized in lab in a form - /// xLab(t) = (x*cosAlp - y*sinAlp) + t*(cosAlp - sinAlp* snp/csp) = xLab0 + t*(cosAlp - sinAlp* snp/csp) - /// yLab(t) = (x*sinAlp + y*cosAlp) + t*(sinAlp + cosAlp* snp/csp) = yLab0 + t*(sinAlp + cosAlp* snp/csp) - /// zLab(t) = z + t * tgl / csp = zLab0 + t * tgl / csp - /// where t is the x-step in the track alpha-frame, xLab,yLab,zLab are reference track coordinates in lab - /// frame (filled by TrackAuxPar for straight line tracks). - /// - /// Therefore, for the parametric track equation in lab 3D we have (wrt tracking-X increment t) - /// xL(t) = xL + t Kx; Kx = (cosAlp - sinAlp* snp/csp) - /// yL(t) = yL + t Ky; Ky = (sinAlp + cosAlp* snp/csp) - /// zL(t) = zL + t Kz; Kz = tgl / csp - /// Note that Kx^2 + Ky^2 = 1 / csp^2 - - const auto& traxH = trax0.rC > trax1.rC ? trax0 : trax1; // circle (for the line rC is set to 0) - const auto& traxL = trax0.rC > trax1.rC ? trax1 : trax0; // line - const auto& trcL = trax0.rC > trax1.rC ? tr1 : tr0; // track of the line - - // solve quadratic equation of line crossing the circle - float dx = traxL.xC - traxH.xC; // X distance between the line lab reference and circle center - float dy = traxL.yC - traxH.yC; // Y... - // t^2(kx^2+ky^2) + 2t(dx*kx+dy*ky) + dx^2 + dy^2 - r^2 = 0 - auto cspi2 = 1. / trcL.getCsp2(); // 1 / csp^2 == kx^2 + ky^2 - auto cspi = std::sqrt(cspi2); - auto tgp = trcL.getSnp() * cspi; - float kx = traxL.c - traxL.s * tgp; - float ky = traxL.s + traxL.c * tgp; - double dk = dx * kx + dy * ky; - double det = dk * dk - cspi2 * (dx * dx + dy * dy - traxH.rC * traxH.rC); - if (det > 0) { // 2 crossings - det = std::sqrt(det); - float t0 = (-dk + det) * cspi2; - float t1 = (-dk - det) * cspi2; - xDCA[0] = traxL.xC + kx * t0; - yDCA[0] = traxL.yC + ky * t0; - xDCA[1] = traxL.xC + kx * t1; - yDCA[1] = traxL.yC + ky * t1; - nDCA = 2; - } else { - // there is no crossing, find the point of the closest approach on the line which is closest to the circle center - float t = -dk * cspi2; - float xL = traxL.xC + kx * t, yL = traxL.yC + ky * t; // point on the line, need to average with point on the circle - float dxc = xL - traxH.xC, dyc = yL - traxH.yC, dist = std::sqrt(dxc * dxc + dyc * dyc); - if (dist - traxH.rC > maxDistXY) { // too large distance - return nDCA; - } - float drcf = traxH.rC / dist; // radius / distance to circle center - float xH = traxH.xC + dxc * drcf, yH = traxH.yC + dyc * drcf; - xDCA[0] = (xL + xH) * 0.5; - yDCA[0] = (yL + yH) * 0.5; - nDCA = 1; - } - return nDCA; - } - - template - int set(const TrackAuxPar& trax0, const T& tr0, const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) - { - // calculate up to 2 crossings between 2 circles - nDCA = 0; - if (trax0.rC > o2::constants::math::Almost0 && trax1.rC > o2::constants::math::Almost0) { // both are not straight lines - nDCA = circlesCrossInfo(trax0, trax1, maxDistXY); - } else if (trax0.rC < o2::constants::math::Almost0 && trax1.rC < o2::constants::math::Almost0) { // both are straigt lines - nDCA = linesCrossInfo(trax0, tr0, trax1, tr1, maxDistXY); - } else { - nDCA = circleLineCrossInfo(trax0, tr0, trax1, tr1, maxDistXY); - } - // - return nDCA; - } - - CrossInfo() = default; - - template - CrossInfo(const TrackAuxPar& trax0, const T& tr0, const TrackAuxPar& trax1, const T& tr1, float maxDistXY = MaxDistXYDef) - { - set(trax0, tr0, trax1, tr1, maxDistXY); - } - ClassDefNV(CrossInfo, 1); -}; - -} // namespace track -} // namespace o2 - -#endif diff --git a/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h b/Detectors/Vertexing/include/DetectorsVertexing/PVertexer.h index 9967cbfcd5642..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); @@ -113,7 +115,7 @@ class PVertexer bool prepareVertexRefit(const TR& tracks, const o2d::VertexBase& vtxSeed); PVertex refitVertex(const std::vector useTrack, const o2d::VertexBase& vtxSeed); - + PVertex refitVertexFull(const std::vector useTrack, const o2d::VertexBase& vtxSeed); auto getNTZClusters() const { return mNTZClustersIni; } auto getTotTrials() const { return mTotTrials; } auto getMaxTrialsPerCluster() const { return mMaxTrialPerCluster; } @@ -135,6 +137,7 @@ class PVertexer void setPoolDumpDirectory(const std::string& d) { mPoolDumpDirectory = d; } void printInpuTracksStatus(const VertexingInput& input) const; + void initMeanVertexConstraint(); private: static constexpr int DBS_UNDEF = -2, DBS_NOISE = -1, DBS_INCHECK = -10; @@ -152,7 +155,6 @@ class PVertexer FitStatus evalIterations(VertexSeed& vtxSeed, PVertex& vtx) const; TimeEst timeEstimate(const VertexingInput& input) const; float findZSeedHistoPeak() const; - void initMeanVertexConstraint(); void applyConstraint(VertexSeed& vtxSeed) const; bool upscaleSigma(VertexSeed& vtxSeed) const; bool relateTrackToMeanVertex(o2::track::TrackParCov& trc, float vtxErr2); @@ -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 5fea1943ac762..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); } //______________________________________________ @@ -1333,6 +1333,37 @@ PVertex PVertexer::refitVertex(const std::vector useTrack, const o2d::Vert return vtxRes; } +//______________________________________________ +PVertex PVertexer::refitVertexFull(const std::vector useTrack, const o2d::VertexBase& vtxSeed) +{ + // Use this method if because of e.g. different alingnment the new vertex is supposed to be shifted from the original one. + // Refit the tracks prepared by the successful prepareVertexRefit, possible skipping those tracks wich have useTrack value false + // (useTrack is ignored if empty). + // The vtxSeed is the originally found vertex, assumed to be the same original PV used for the prepareVertexRefit. + // Refitted PrimaryVertex is returned, negative chi2 means failure of the refit. + // ATTENTION: only the position is refitted, the vertex time and IRMin/IRMax info is dummy. + + if (vtxSeed != mVtxRefitOrig) { + throw std::runtime_error("refitVertex must be preceded by successful prepareVertexRefit"); + } + VertexingInput inp; + inp.scaleSigma2 = mPVParams->iniScale2; + inp.idRange = gsl::span(mRefitTrackIDs); + if (useTrack.size()) { + for (uint32_t i = 0; i < mTracksPool.size(); i++) { + mTracksPool[i].vtxID = useTrack[mTracksPool[i].entry] ? TrackVF::kNoVtx : TrackVF::kDiscarded; + } + } + PVertex vtxRes{}; + vtxRes.VertexBase::operator=(vtxSeed); + if (findVertex(inp, vtxRes)) { + vtxRes.setTimeStamp({0.f, -1.}); // time is not refitter + } else { + vtxRes.setChi2(-1.); + } + return vtxRes; +} + //______________________________________________ void PVertexer::printInpuTracksStatus(const VertexingInput& input) const { diff --git a/Detectors/Vertexing/src/SVertexer.cxx b/Detectors/Vertexing/src/SVertexer.cxx index 1d48bcceb0097..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" @@ -269,6 +269,9 @@ void SVertexer::updateTimeDependentParams() if (!updatedOnce) { updatedOnce = true; mSVParams = &SVertexerParams::Instance(); + if (mSVParams->mExcludeTPCtracks && !mRecoCont->isTrackSourceLoaded(GIndex::TPC)) { + LOGP(fatal, "TPC tracks requested but not provided"); + } // precalculated selection cuts mMinR2ToMeanVertex = mSVParams->minRToMeanVertex * mSVParams->minRToMeanVertex; mMaxR2ToMeanVertexCascV0 = mSVParams->maxRToMeanVertexCascV0 * mSVParams->maxRToMeanVertexCascV0; @@ -328,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; } //__________________________________________________________________ @@ -455,8 +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->setTrackReferenceX(900); // disable propagation after refit by setting reference to value > 500 + 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; @@ -543,8 +545,7 @@ void SVertexer::buildT2V(const o2::globaltracking::RecoContainer& recoData) // a } continue; } - - if (!hasTPC && nITSclu < mSVParams->mITSSAminNclu && (!shortOBITSOnlyTrack || mSVParams->mRejectITSonlyOBtrack)) { + if ((isTPCloaded && !hasTPC) && (isITSloaded && (nITSclu < mSVParams->mITSSAminNclu && (!shortOBITSOnlyTrack || mSVParams->mRejectITSonlyOBtrack)))) { continue; // reject short ITS-only } @@ -1359,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/ZDC/reconstruction/include/ZDCReconstruction/CTFCoder.h b/Detectors/ZDC/reconstruction/include/ZDCReconstruction/CTFCoder.h index c2f2163600f29..ec80c16af329e 100644 --- a/Detectors/ZDC/reconstruction/include/ZDCReconstruction/CTFCoder.h +++ b/Detectors/ZDC/reconstruction/include/ZDCReconstruction/CTFCoder.h @@ -32,10 +32,10 @@ namespace o2 namespace zdc { -class CTFCoder : public o2::ctf::CTFCoderBase +class CTFCoder final : public o2::ctf::CTFCoderBase { public: - CTFCoder(o2::ctf::CTFCoderBase::OpType op) : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::ZDC) {} + CTFCoder(o2::ctf::CTFCoderBase::OpType op, const std::string& ctfdictOpt = "none") : o2::ctf::CTFCoderBase(op, CTF::getNBlocks(), o2::detectors::DetID::ZDC, 1.f, ctfdictOpt) {} ~CTFCoder() final = default; /// entropy-encode data to buffer with CTF diff --git a/Detectors/ZDC/simulation/src/Detector.cxx b/Detectors/ZDC/simulation/src/Detector.cxx index c6f91d9c2164f..b8b81379a4dff 100644 --- a/Detectors/ZDC/simulation/src/Detector.cxx +++ b/Detectors/ZDC/simulation/src/Detector.cxx @@ -2355,7 +2355,7 @@ void Detector::createDetectors() // --- Positioning the ZEM into the ZDC - rotation for 90 degrees // NB -> ZEM is positioned in cave volume - const float z0 = 1313.3475; // center of caveRB24 mother volume + const float z0 = 1313.3475 + 75.; // center of caveRB24 mother volume TVirtualMC::GetMC()->Gspos("ZEM ", 1, "caveRB24", -Geometry::ZEMPOSITION[0], Geometry::ZEMPOSITION[1], Geometry::ZEMPOSITION[2] + Geometry::ZEMDIMENSION[0] - z0, irotzem1, "ONLY"); // Second EM ZDC (same side w.r.t. IP, just on the other side w.r.t. beam pipe) diff --git a/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyDecoderSpec.h b/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyDecoderSpec.h index ae53ca8bdd0fb..6226b4dc99fe3 100644 --- a/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyDecoderSpec.h +++ b/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyDecoderSpec.h @@ -28,7 +28,7 @@ namespace zdc class EntropyDecoderSpec : public o2::framework::Task { public: - EntropyDecoderSpec(int verbosity); + EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt = "none"); ~EntropyDecoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -41,7 +41,7 @@ class EntropyDecoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec); +framework::DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt); } // namespace zdc } // namespace o2 diff --git a/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyEncoderSpec.h b/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyEncoderSpec.h index 4979de5a30332..44c4585bf0c3f 100644 --- a/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyEncoderSpec.h +++ b/Detectors/ZDC/workflow/include/ZDCWorkflow/EntropyEncoderSpec.h @@ -29,7 +29,7 @@ namespace zdc class EntropyEncoderSpec : public o2::framework::Task { public: - EntropyEncoderSpec(bool selIR); + EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt = "none"); ~EntropyEncoderSpec() override = default; void run(o2::framework::ProcessingContext& pc) final; void init(o2::framework::InitContext& ic) final; @@ -43,7 +43,7 @@ class EntropyEncoderSpec : public o2::framework::Task }; /// create a processor spec -framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false); +framework::DataProcessorSpec getEntropyEncoderSpec(bool selIR = false, const std::string& ctfdictOpt = "none"); } // namespace zdc } // namespace o2 diff --git a/Detectors/ZDC/workflow/src/EntropyDecoderSpec.cxx b/Detectors/ZDC/workflow/src/EntropyDecoderSpec.cxx index bf870324ce442..59c774662525a 100644 --- a/Detectors/ZDC/workflow/src/EntropyDecoderSpec.cxx +++ b/Detectors/ZDC/workflow/src/EntropyDecoderSpec.cxx @@ -25,8 +25,7 @@ namespace o2 { namespace zdc { - -EntropyDecoderSpec::EntropyDecoderSpec(int verbosity) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder) +EntropyDecoderSpec::EntropyDecoderSpec(int verbosity, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Decoder, ctfdictOpt) { mTimer.Stop(); mTimer.Reset(); @@ -81,7 +80,7 @@ void EntropyDecoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) +DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec, const std::string& ctfdictOpt) { std::vector outputs{ OutputSpec{{"trig"}, "ZDC", "DIGITSBC", 0, Lifetime::Timeframe}, @@ -91,17 +90,18 @@ DataProcessorSpec getEntropyDecoderSpec(int verbosity, unsigned int sspec) std::vector inputs; inputs.emplace_back("ctf_ZDC", "ZDC", "CTFDATA", sspec, Lifetime::Timeframe); - inputs.emplace_back("ctfdict_ZDC", "ZDC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("ZDC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict_ZDC", "ZDC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("ZDC/Calib/CTFDictionaryTree")); + } inputs.emplace_back("trigoffset", "CTP", "Trig_Offset", 0, Lifetime::Condition, ccdbParamSpec("CTP/Config/TriggerOffsets")); return DataProcessorSpec{ "zdc-entropy-decoder", inputs, outputs, - AlgorithmSpec{adaptFromTask(verbosity)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; + AlgorithmSpec{adaptFromTask(verbosity, ctfdictOpt)}, + Options{{"ans-version", VariantType::String, {"version of ans entropy coder implementation to use"}}}}; } - } // namespace zdc } // namespace o2 diff --git a/Detectors/ZDC/workflow/src/EntropyEncoderSpec.cxx b/Detectors/ZDC/workflow/src/EntropyEncoderSpec.cxx index abbd821fcb749..1a12360645ab2 100644 --- a/Detectors/ZDC/workflow/src/EntropyEncoderSpec.cxx +++ b/Detectors/ZDC/workflow/src/EntropyEncoderSpec.cxx @@ -27,8 +27,7 @@ namespace o2 { namespace zdc { - -EntropyEncoderSpec::EntropyEncoderSpec(bool selIR) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder), mSelIR(selIR) +EntropyEncoderSpec::EntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) : mCTFCoder(o2::ctf::CTFCoderBase::OpType::Encoder, ctfdictOpt), mSelIR(selIR) { mTimer.Stop(); mTimer.Reset(); @@ -74,13 +73,16 @@ void EntropyEncoderSpec::endOfStream(EndOfStreamContext& ec) mTimer.CpuTime(), mTimer.RealTime(), mTimer.Counter() - 1); } -DataProcessorSpec getEntropyEncoderSpec(bool selIR) +DataProcessorSpec getEntropyEncoderSpec(bool selIR, const std::string& ctfdictOpt) { std::vector inputs; inputs.emplace_back("trig", "ZDC", "DIGITSBC", 0, Lifetime::Timeframe); inputs.emplace_back("chan", "ZDC", "DIGITSCH", 0, Lifetime::Timeframe); inputs.emplace_back("peds", "ZDC", "DIGITSPD", 0, Lifetime::Timeframe); - inputs.emplace_back("ctfdict", "ZDC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("ZDC/Calib/CTFDictionaryTree")); + + if (ctfdictOpt.empty() || ctfdictOpt == "ccdb") { + inputs.emplace_back("ctfdict", "ZDC", "CTFDICT", 0, Lifetime::Condition, ccdbParamSpec("ZDC/Calib/CTFDictionaryTree")); + } if (selIR) { inputs.emplace_back("selIRFrames", "CTF", "SELIRFRAMES", 0, Lifetime::Timeframe); } @@ -89,13 +91,11 @@ DataProcessorSpec getEntropyEncoderSpec(bool selIR) inputs, Outputs{{"ZDC", "CTFDATA", 0, Lifetime::Timeframe}, {{"ctfrep"}, "ZDC", "CTFENCREP", 0, Lifetime::Timeframe}}, - AlgorithmSpec{adaptFromTask(selIR)}, - Options{{"ctf-dict", VariantType::String, "ccdb", {"CTF dictionary: empty or ccdb=CCDB, none=no external dictionary otherwise: local filename"}}, - {"irframe-margin-bwd", VariantType::UInt32, 0u, {"margin in BC to add to the IRFrame lower boundary when selection is requested"}}, + AlgorithmSpec{adaptFromTask(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"}}}}; } - } // namespace zdc } // namespace o2 diff --git a/Detectors/ZDC/workflow/src/entropy-encoder-workflow.cxx b/Detectors/ZDC/workflow/src/entropy-encoder-workflow.cxx index 070c65ac9196a..6f73fa121244f 100644 --- a/Detectors/ZDC/workflow/src/entropy-encoder-workflow.cxx +++ b/Detectors/ZDC/workflow/src/entropy-encoder-workflow.cxx @@ -23,6 +23,7 @@ void customize(std::vector& workflowOptions) // option allowing to set parameters std::vector options{ 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"}}}; std::swap(workflowOptions, options); @@ -38,6 +39,6 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) // Update the (declared) parameters if changed from the command line o2::conf::ConfigurableParam::updateFromString(cfgc.options().get("configKeyValues")); bool selIR = cfgc.options().get("select-ir-frames"); - wf.emplace_back(o2::zdc::getEntropyEncoderSpec(selIR)); + wf.emplace_back(o2::zdc::getEntropyEncoderSpec(selIR, cfgc.options().get("ctf-dict"))); return wf; } diff --git a/Detectors/gconfig/g4Config.C b/Detectors/gconfig/g4Config.C index 8f74c0105dbf5..c2b1fbd433e4b 100644 --- a/Detectors/gconfig/g4Config.C +++ b/Detectors/gconfig/g4Config.C @@ -100,7 +100,16 @@ void Config() auto& g4Params = ::o2::conf::G4Params::Instance(); auto& physicsSetup = g4Params.getPhysicsConfigString(); std::cout << "PhysicsSetup wanted " << physicsSetup << "\n"; - auto runConfiguration = new TG4RunConfiguration("geomRoot", physicsSetup, "stepLimiter+specialCuts", + std::string geomNavStr; + if (g4Params.navmode == o2::conf::EG4Nav::kTGeo) { + geomNavStr = "geomRoot"; + } else if (g4Params.navmode == o2::conf::EG4Nav::kG4) { + geomNavStr = "geomVMC+RootToGeant4"; + } else { + LOG(fatal) << "Unsupported geometry navigation mode"; + } + + auto runConfiguration = new TG4RunConfiguration(geomNavStr, physicsSetup, "stepLimiter+specialCuts", specialStacking, mtMode); /// avoid the use of G4BACKTRACE (it seems to inferfere with process logic in o2-sim) setenv("G4BACKTRACE", "none", 1); 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/Base/src/DirectoryLoader.cxx b/EventVisualisation/Base/src/DirectoryLoader.cxx index 1b0135428806f..e106eaf7ebb47 100644 --- a/EventVisualisation/Base/src/DirectoryLoader.cxx +++ b/EventVisualisation/Base/src/DirectoryLoader.cxx @@ -14,6 +14,8 @@ /// \author julian.myrcha@cern.ch #include "EventVisualisationBase/DirectoryLoader.h" +#include "Framework/DefaultsHelpers.h" +#include "Framework/DataTakingContext.h" #include #include #include @@ -29,10 +31,14 @@ using namespace o2::event_visualisation; deque DirectoryLoader::load(const std::string& path, const std::string& marker, const std::vector& ext) { deque result; - for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { - result.push_back(entry.path().filename()); + try { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { + result.push_back(entry.path().filename()); + } } + } catch (std::filesystem::filesystem_error const& ex) { + LOGF(error, "filesystem problem during DirectoryLoader::load: %s", ex.what()); } // comparison with safety if marker not in the filename (-1+1 gives 0) std::sort(result.begin(), result.end(), @@ -56,15 +62,18 @@ bool DirectoryLoader::canCreateNextFile(const std::vector& paths, c } } } catch (std::filesystem::filesystem_error const& ex) { - LOGF(info, "filesystem problem: %s", ex.what()); + LOGF(error, "filesystem problem during DirectoryLoader::canCreateNextFile: %s", ex.what()); } } // comparison with safety if marker not in the filename (-1+1 gives 0) - std::ranges::sort(result.begin(), result.end(), - [marker](const std::string& a, const std::string& b) { - return a.substr(a.find_first_of(marker) + 1) > b.substr(b.find_first_of(marker) + 1); - }); + if (result.size() > 1) { + std::ranges::sort(result.begin(), result.end(), + [marker](const std::string& a, const std::string& b) { + return a.substr(a.find_first_of(marker) + 1) > b.substr(b.find_first_of(marker) + 1); + }); + } + unsigned long accumulatedSize = 0L; const std::regex delimiter{"_"}; for (auto const& file : result) { @@ -87,12 +96,16 @@ bool DirectoryLoader::canCreateNextFile(const std::vector& paths, c deque DirectoryLoader::load(const std::vector& paths, const std::string& marker, const std::vector& ext) { deque result; - for (const auto& path : paths) { - for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { - result.push_back(entry.path().filename()); + try { + for (const auto& path : paths) { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { + result.push_back(entry.path().filename()); + } } } + } catch (std::filesystem::filesystem_error const& ex) { + LOGF(error, "filesystem problem during DirectoryLoader::load: %s", ex.what()); } // comparison with safety if marker not in the filename (-1+1 gives 0) std::sort(result.begin(), result.end(), @@ -105,11 +118,15 @@ deque DirectoryLoader::load(const std::vector& paths, const std::vector DirectoryLoader::allFolders(const std::string& location) { - auto const pos = location.find_last_of('_'); std::vector folders; - folders.push_back(location.substr(0, pos) + "_PHYSICS"); - folders.push_back(location.substr(0, pos) + "_COSMICS"); - folders.push_back(location.substr(0, pos) + "_SYNTHETIC"); + if (o2::framework::DefaultsHelpers::deploymentMode() == o2::framework::DeploymentMode::OnlineDDS) { + auto const pos = location.find_last_of('_'); + folders.push_back(location.substr(0, pos) + "_PHYSICS"); + folders.push_back(location.substr(0, pos) + "_COSMICS"); + folders.push_back(location.substr(0, pos) + "_SYNTHETIC"); + } else { + folders.push_back(location); + } return folders; } @@ -135,10 +152,14 @@ std::time_t to_time_t(TP tp) int DirectoryLoader::getNumberOfFiles(const std::string& path, std::vector& ext) { int res = 0; - for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { - res++; + try { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (std::find(ext.begin(), ext.end(), entry.path().extension()) != ext.end()) { + res++; + } } + } catch (std::filesystem::filesystem_error const& ex) { + LOGF(error, "filesystem problem during DirectoryLoader::getNumberOfFiles: %s", ex.what()); } return res; } @@ -160,8 +181,12 @@ std::string DirectoryLoader::getLatestFile(const std::string& path, std::vector< void DirectoryLoader::removeOldestFiles(const std::string& path, std::vector& ext, const int remaining) { - while (getNumberOfFiles(path, ext) > remaining) { - LOGF(info, "removing oldest file in folder: %s : %s", path, getLatestFile(path, ext)); - filesystem::remove(path + "/" + getLatestFile(path, ext)); + try { + while (getNumberOfFiles(path, ext) > remaining) { + LOGF(info, "removing oldest file in folder: %s : %s", path, getLatestFile(path, ext)); + filesystem::remove(path + "/" + getLatestFile(path, ext)); + } + } catch (std::filesystem::filesystem_error const& ex) { + LOGF(error, "filesystem problem during DirectoryLoader::removeOldestFiles: %s", ex.what()); } } diff --git a/EventVisualisation/DataConverter/src/VisualisationEventROOTSerializer.cxx b/EventVisualisation/DataConverter/src/VisualisationEventROOTSerializer.cxx index 8c1a84c1bf85e..8480e15ee9772 100644 --- a/EventVisualisation/DataConverter/src/VisualisationEventROOTSerializer.cxx +++ b/EventVisualisation/DataConverter/src/VisualisationEventROOTSerializer.cxx @@ -97,6 +97,10 @@ void VisualisationEventROOTSerializer::toFile(const VisualisationEvent& event, L { std::string fileName = location.fileName(); TFile f(fileName.c_str(), "recreate"); + if (f.IsZombie()) { + LOGF(error, "Could not create output file %s", fileName.c_str()); + return; + } saveInt("runNumber", event.mRunNumber); saveInt("runType", event.mRunType); 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 dedbf8cb590b2..956c4a44c5684 100644 --- a/Framework/AnalysisSupport/CMakeLists.txt +++ b/Framework/AnalysisSupport/CMakeLists.txt @@ -16,9 +16,16 @@ if(TARGET JAliEn::JAliEn) set(EXTRA_TARGETS XRootD::Client JAliEn::JAliEn) endif() +o2_add_library(FrameworkOnDemandTablesSupport + SOURCES src/OnDemandPlugin.cxx + src/AODReaderHelpers.cxx + PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src + PUBLIC_LINK_LIBRARIES O2::Framework ${EXTRA_TARGETS}) + o2_add_library(FrameworkAnalysisSupport SOURCES src/Plugin.cxx src/DataInputDirector.cxx + src/TableTreeHelpers.cxx src/AODJAlienReaderHelpers.cxx src/AODWriterHelpers.cxx PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src @@ -39,3 +46,18 @@ o2_add_test(DataInputDirector NAME test_Framework_test_DataInputDirector COMPONENT_NAME Framework 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 + LABELS framework + PUBLIC_LINK_LIBRARIES O2::FrameworkAnalysisSupport benchmark::benchmark) diff --git a/Framework/Core/include/Framework/TableTreeHelpers.h b/Framework/AnalysisSupport/include/Framework/TableTreeHelpers.h similarity index 76% rename from Framework/Core/include/Framework/TableTreeHelpers.h rename to Framework/AnalysisSupport/include/Framework/TableTreeHelpers.h index 3f76298a5bbd4..c5e9d5fa14261 100644 --- a/Framework/Core/include/Framework/TableTreeHelpers.h +++ b/Framework/AnalysisSupport/include/Framework/TableTreeHelpers.h @@ -18,7 +18,7 @@ #include "TTreeReader.h" #include "TTreeReaderValue.h" #include "TTreeReaderArray.h" -#include "TableBuilder.h" +#include "Framework/TableBuilder.h" #include #include @@ -91,30 +91,6 @@ class TableToTree std::vector> mColumnReaders; }; -class FragmentToBatch -{ - public: - // The function to be used to create the required stream. - using StreamerCreator = std::function(std::shared_ptr, const std::shared_ptr& buffer)>; - - FragmentToBatch(StreamerCreator, std::shared_ptr, arrow::MemoryPool* pool = arrow::default_memory_pool()); - void setLabel(const char* label); - void fill(std::shared_ptr dataSetSchema, std::shared_ptr); - std::shared_ptr finalize(); - - std::shared_ptr streamer(std::shared_ptr buffer) - { - return mCreator(mFragment, buffer); - } - - private: - std::shared_ptr mFragment; - arrow::MemoryPool* mArrowMemoryPool = nullptr; - std::string mTableLabel; - std::shared_ptr mRecordBatch; - StreamerCreator mCreator; -}; - // ----------------------------------------------------------------------------- } // namespace o2::framework diff --git a/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx b/Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx index 85ed9cd573d8a..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" @@ -98,29 +101,6 @@ using o2::monitoring::tags::Value; namespace o2::framework::readers { -auto setEOSCallback(InitContext& ic) -{ - ic.services().get().set( - [](EndOfStreamContext& eosc) { - auto& control = eosc.services().get(); - control.endOfStream(); - control.readyToQuit(QuitRequest::Me); - }); -} - -template -static inline auto extractTypedOriginal(ProcessingContext& pc) -{ - /// FIXME: this should be done in invokeProcess() as some of the originals may be compound tables - return O{pc.inputs().get(aod::MetadataTrait::metadata::tableLabel())->asArrowTable()}; -} - -template -static inline auto extractOriginalsTuple(framework::pack, ProcessingContext& pc) -{ - return std::make_tuple(extractTypedOriginal(pc)...); -} - AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const& ctx) { // aod-parent-base-path-replacement is now a workflow option, so it needs to be @@ -134,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}); @@ -145,6 +146,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const stats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_DESTROYED), DataProcessingStats::Op::Set, 0}); stats.updateStats({static_cast(ProcessingStatsId::ARROW_MESSAGES_DESTROYED), DataProcessingStats::Op::Set, 0}); stats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_EXPIRED), DataProcessingStats::Op::Set, 0}); + stats.updateStats({static_cast(ProcessingStatsId::CONSUMED_TIMEFRAMES), DataProcessingStats::Op::Set, 0}); if (!options.isSet("aod-file-private")) { LOGP(fatal, "No input file defined!"); @@ -156,7 +158,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const auto maxRate = options.get("aod-max-io-rate"); // create a DataInputDirector - auto didir = std::make_shared(filename, &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)) { @@ -198,7 +200,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const numTF, watchdog, maxRate, - didir, reportTFN, reportTFFileName](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device) { + didir, reportTFN, reportTFFileName](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device, DataProcessingStats& dpstats) { // Each parallel reader device.inputTimesliceId reads the files fileCounter*device.maxInputTimeslices+device.inputTimesliceId // the TF to read is numTF assert(device.inputTimesliceId < device.maxInputTimeslices); @@ -301,6 +303,10 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const } } totalDFSent++; + + // Use the new API for sending TIMESLICE_NUMBER_STARTED + dpstats.updateStats({(int)ProcessingStatsId::TIMESLICE_NUMBER_STARTED, DataProcessingStats::Op::Add, 1}); + dpstats.processCommandQueue(); monitoring.send(Metric{(uint64_t)totalDFSent, "df-sent"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL)); monitoring.send(Metric{(uint64_t)totalSizeUncompressed / 1000, "aod-bytes-read-uncompressed"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL)); monitoring.send(Metric{(uint64_t)totalSizeCompressed / 1000, "aod-bytes-read-compressed"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL)); diff --git a/Framework/AnalysisSupport/src/AODReaderHelpers.cxx b/Framework/AnalysisSupport/src/AODReaderHelpers.cxx new file mode 100644 index 0000000000000..4c1c065000186 --- /dev/null +++ b/Framework/AnalysisSupport/src/AODReaderHelpers.cxx @@ -0,0 +1,202 @@ +// 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 "AODReaderHelpers.h" +#include "../src/ExpressionJSONHelpers.h" +#include "../src/IndexJSONHelpers.h" + +#include "Framework/AnalysisDataModel.h" +#include "Framework/AnalysisHelpers.h" +#include "Framework/DataProcessingHelpers.h" +#include "Framework/AlgorithmSpec.h" +#include "Framework/DataSpecUtils.h" +#include "Framework/DataSpecViews.h" +#include "Framework/ConfigContext.h" +#include "Framework/DanglingEdgesContext.h" + +namespace o2::framework::readers +{ +namespace +{ +struct Buildable { + bool exclusive = false; + std::string binding; + std::vector labels; + std::vector matchers; + header::DataOrigin origin; + header::DataDescription description; + header::DataHeader::SubSpecificationType version; + std::vector records; + std::shared_ptr outputSchema; + + explicit Buildable(InputSpec const& spec) + : binding{spec.binding} + { + auto&& [origin_, description_, version_] = DataSpecUtils::asConcreteDataMatcher(spec); + origin = origin_; + description = description_; + version = version_; + + auto loc = std::find_if(spec.metadata.begin(), spec.metadata.end(), [](ConfigParamSpec const& cps) { return cps.name.compare("index-records") == 0; }); + std::stringstream iws(loc->defaultValue.get()); + records = IndexJSONHelpers::read(iws); + + loc = std::find_if(spec.metadata.begin(), spec.metadata.end(), [](ConfigParamSpec const& cps) { return cps.name.compare("index-exclusive") == 0; }); + exclusive = loc->defaultValue.get(); + + for (auto const& r : records) { + labels.emplace_back(r.label); + matchers.emplace_back(r.matcher); + } + outputSchema = std::make_shared([](std::vector const& recs) { + std::vector> fields; + fields.reserve(recs.size()); + std::ranges::transform(recs, std::back_inserter(fields), [](auto& r) { return r.field(); }); + return fields; + }(records)) + ->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{binding}})); + } + + framework::Builder createBuilder() const + { + return { + exclusive, + labels, + matchers, + records, + outputSchema, + origin, + description, + version, + nullptr}; + } +}; + +} // namespace + +AlgorithmSpec AODReaderHelpers::indexBuilderCallback(ConfigContext const& /*ctx*/) +{ + return AlgorithmSpec::InitCallback{[](InitContext& ic) { + auto const& requested = ic.services().get().requestedIDXs; + std::vector builders; + builders.reserve(requested.size()); + std::ranges::transform(requested, std::back_inserter(builders), [](auto const& i) { return Buildable{i}.createBuilder(); }); + return [builders](ProcessingContext& pc) mutable { + auto outputs = pc.outputs(); + std::ranges::for_each(builders, [&pc, &outputs](auto& builder) { outputs.adopt(Output{builder.origin, builder.description, builder.version}, builder.materialize(pc)); }); + }; + }}; +} + +namespace +{ +struct Spawnable { + std::string binding; + std::vector labels; + std::vector matchers; + std::vector projectors; + std::vector> expressions; + std::shared_ptr outputSchema; + std::shared_ptr inputSchema; + + header::DataOrigin origin; + header::DataDescription description; + header::DataHeader::SubSpecificationType version; + + explicit Spawnable(InputSpec const& spec) + : binding{spec.binding} + { + auto&& [origin_, description_, version_] = DataSpecUtils::asConcreteDataMatcher(spec); + origin = origin_; + description = description_; + version = version_; + auto loc = std::find_if(spec.metadata.begin(), spec.metadata.end(), [](ConfigParamSpec const& cps) { return cps.name.compare("projectors") == 0; }); + std::stringstream iws(loc->defaultValue.get()); + projectors = ExpressionJSONHelpers::read(iws); + + loc = std::find_if(spec.metadata.begin(), spec.metadata.end(), [](ConfigParamSpec const& cps) { return cps.name.compare("schema") == 0; }); + iws.clear(); + iws.str(loc->defaultValue.get()); + outputSchema = ArrowJSONHelpers::read(iws); + o2::framework::addLabelToSchema(outputSchema, binding.c_str()); + + std::vector> schemas; + for (auto const& i : spec.metadata | views::filter_string_params_starts_with("input-schema:")) { + labels.emplace_back(i.name.substr(13)); + iws.clear(); + auto json = i.defaultValue.get(); + iws.str(json); + schemas.emplace_back(ArrowJSONHelpers::read(iws)); + } + std::ranges::transform(spec.metadata | + views::filter_string_params_starts_with("input:") | + std::ranges::views::transform( + [](auto const& param) { + return DataSpecUtils::fromMetadataString(param.defaultValue.template get()); + }), + std::back_inserter(matchers), [](auto const& i) { return std::get(i.matcher); }); + + std::vector> fields; + std::ranges::for_each(schemas, + [&fields](auto const& s) { + std::ranges::copy(s->fields(), std::back_inserter(fields)); + }); + + inputSchema = std::make_shared(fields); + expressions = expressions::materializeProjectors(projectors, inputSchema, outputSchema->fields()); + } + + std::shared_ptr makeProjector() const + { + std::shared_ptr p = nullptr; + auto s = gandiva::Projector::Make( + inputSchema, + expressions, + &p); + if (!s.ok()) { + throw o2::framework::runtime_error_f("Failed to create projector: %s", s.ToString().c_str()); + } + return p; + } + + framework::Spawner createMaker() const + { + return { + binding, + labels, + matchers, + expressions, + makeProjector(), + outputSchema, + inputSchema, + origin, + description, + version}; + } +}; + +} // namespace + +AlgorithmSpec AODReaderHelpers::aodSpawnerCallback(ConfigContext const& /*ctx*/) +{ + return AlgorithmSpec::InitCallback{[](InitContext& ic) { + auto const& requested = ic.services().get().spawnerInputs; + std::vector spawners; + spawners.reserve(requested.size()); + std::ranges::transform(requested, std::back_inserter(spawners), [](auto const& i) { return Spawnable{i}.createMaker(); }); + return [spawners](ProcessingContext& pc) mutable { + auto outputs = pc.outputs(); + std::ranges::for_each(spawners, [&pc, &outputs](auto& spawner) { outputs.adopt(Output{spawner.origin, spawner.description, spawner.version}, spawner.materialize(pc)); }); + }; + }}; +} + +} // namespace o2::framework::readers diff --git a/Framework/Core/include/Framework/AODReaderHelpers.h b/Framework/AnalysisSupport/src/AODReaderHelpers.h similarity index 76% rename from Framework/Core/include/Framework/AODReaderHelpers.h rename to Framework/AnalysisSupport/src/AODReaderHelpers.h index 957a5b1cd25ba..848ef6b696713 100644 --- a/Framework/Core/include/Framework/AODReaderHelpers.h +++ b/Framework/AnalysisSupport/src/AODReaderHelpers.h @@ -12,20 +12,16 @@ #ifndef O2_FRAMEWORK_AODREADERHELPERS_H_ #define O2_FRAMEWORK_AODREADERHELPERS_H_ -#include "Framework/TableBuilder.h" #include "Framework/AlgorithmSpec.h" -#include "Framework/Logger.h" -#include "Framework/RootMessageContext.h" #include namespace o2::framework::readers { - struct AODReaderHelpers { static AlgorithmSpec rootFileReaderCallback(); - static AlgorithmSpec aodSpawnerCallback(std::vector& requested); - static AlgorithmSpec indexBuilderCallback(std::vector& requested); + static AlgorithmSpec aodSpawnerCallback(ConfigContext const& /*ctx*/); + static AlgorithmSpec indexBuilderCallback(ConfigContext const& /*ctx*/); }; } // namespace o2::framework::readers diff --git a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx index 40d2189ea96d0..5b5829d96a1de 100644 --- a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx @@ -8,7 +8,7 @@ // 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/AnalysisContext.h" +#include "Framework/DanglingEdgesContext.h" #include "Framework/ConfigContext.h" #include "Framework/ControlService.h" #include "AODWriterHelpers.h" @@ -21,7 +21,10 @@ #include "Framework/TableConsumer.h" #include "Framework/DataOutputDirector.h" #include "Framework/TableTreeHelpers.h" +#include "Framework/Signpost.h" +#include +#include #include #include #include @@ -29,6 +32,8 @@ #include #include +O2_DECLARE_DYNAMIC_LOG(histogram_registry); + namespace o2::framework::writers { @@ -44,6 +49,7 @@ struct InputObjectRoute { struct InputObject { TClass* kind = nullptr; void* obj = nullptr; + std::string container; std::string name; int count = -1; }; @@ -53,13 +59,13 @@ const static std::unordered_map ROOTfileNa AlgorithmSpec AODWriterHelpers::getOutputTTreeWriter(ConfigContext const& ctx) { - auto& ac = ctx.services().get(); auto dod = AnalysisSupportHelpers::getDataOutputDirector(ctx); int compressionLevel = 505; if (ctx.options().hasOption("aod-writer-compression")) { compressionLevel = ctx.options().get("aod-writer-compression"); } - return AlgorithmSpec{[dod, outputInputs = ac.outputsInputsAOD, compressionLevel](InitContext& ic) -> std::function { + return AlgorithmSpec{[dod, compressionLevel](InitContext& ic) -> std::function { + auto outputInputs = ic.services().get().outputsInputsAOD; LOGP(debug, "======== getGlobalAODSink::Init =========="); // find out if any table needs to be saved @@ -176,13 +182,12 @@ AlgorithmSpec AODWriterHelpers::getOutputTTreeWriter(ConfigContext const& ctx) } // get the TableConsumer and corresponding arrow table - auto msg = pc.inputs().get(ref.spec->binding); - if (msg.header == nullptr) { + if (ref.header == nullptr) { LOGP(error, "No header for message {}:{}", ref.spec->binding, DataSpecUtils::describe(*ref.spec)); continue; } - auto s = pc.inputs().get(ref.spec->binding); - auto table = s->asArrowTable(); + + auto table = pc.inputs().get(std::get(ref.spec->matcher))->asArrowTable(); if (!table->Validate().ok()) { LOGP(warning, "The table \"{}\" is not valid and will not be saved!", tableName); continue; @@ -233,13 +238,13 @@ AlgorithmSpec AODWriterHelpers::getOutputTTreeWriter(ConfigContext const& ctx) }; } -AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& ctx) +AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& /*ctx*/) { - auto& ac = ctx.services().get(); - auto tskmap = ac.outTskMap; - auto objmap = ac.outObjHistMap; - - return AlgorithmSpec{[objmap, tskmap](InitContext& ic) -> std::function { + return AlgorithmSpec{[](InitContext& ic) -> std::function { + using namespace monitoring; + auto& dec = ic.services().get(); + auto tskmap = dec.outTskMap; + auto objmap = dec.outObjHistMap; auto& callbacks = ic.services().get(); auto inputObjects = std::make_shared>>(); @@ -270,24 +275,30 @@ AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& ctx) callbacks.set(endofdatacb); return [inputObjects, objmap, tskmap](ProcessingContext& pc) mutable -> void { auto mergePart = [&inputObjects, &objmap, &tskmap](DataRef const& ref) { + O2_SIGNPOST_ID_GENERATE(hid, histogram_registry); + O2_SIGNPOST_START(histogram_registry, hid, "mergePart", "Merging histogram"); if (!ref.header) { - LOG(error) << "Header not found"; + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "Header not found."); return; } auto datah = o2::header::get(ref.header); if (!datah) { - LOG(error) << "No data header in stack"; + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "No data header in stack"); return; } if (!ref.payload) { - LOGP(error, "Payload not found for {}/{}/{}", datah->dataOrigin.as(), datah->dataDescription.as(), datah->subSpecification); + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "Payload not found for %{public}s/%{public}s/%d", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); return; } auto objh = o2::header::get(ref.header); if (!objh) { - LOGP(error, "No output object header in stack of {}/{}/{}", datah->dataOrigin.as(), datah->dataDescription.as(), datah->subSpecification); + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "No output object header in stack of %{public}s/%{public}s/%d.", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); return; } @@ -297,48 +308,75 @@ AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& ctx) obj.kind = tm.ReadClass(); tm.SetBufferOffset(0); tm.ResetMap(); + O2_SIGNPOST_ID_GENERATE(did, histogram_registry); + O2_SIGNPOST_START(histogram_registry, did, "initialising root", "Starting deserialization of %{public}s/%{public}s/%d", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); if (obj.kind == nullptr) { - LOGP(error, "Cannot read class info from buffer of {}/{}/{}", datah->dataOrigin.as(), datah->dataDescription.as(), datah->subSpecification); + O2_SIGNPOST_END(histogram_registry, did, "initialising root", "Failed to deserialise"); + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "Cannot read class info from buffer of %{public}s/%{public}s/%d.", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); return; } + O2_SIGNPOST_END(histogram_registry, did, "initialising root", "Done init."); auto policy = objh->mPolicy; auto sourceType = objh->mSourceType; auto hash = objh->mTaskHash; + O2_SIGNPOST_START(histogram_registry, did, "deserialization", "Starting deserialization of %{public}s/%{public}s/%d", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); obj.obj = tm.ReadObjectAny(obj.kind); auto* named = static_cast(obj.obj); obj.name = named->GetName(); + O2_SIGNPOST_END(histogram_registry, did, "deserialization", "Done deserialization."); + // If we have a folder, we assume the first element of the path + // to be the name of the registry. + bool folderForContainer = false; + if (sourceType == HistogramRegistrySource) { + folderForContainer = objh->createContainer != 0; + obj.container = objh->containerName; + } else { + obj.container = obj.name; + } auto hpos = std::find_if(tskmap.begin(), tskmap.end(), [&](auto&& x) { return x.id == hash; }); if (hpos == tskmap.end()) { - LOG(error) << "No task found for hash " << hash; + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "No task found for hash %d.", hash); return; } auto taskname = hpos->name; auto opos = std::find_if(objmap.begin(), objmap.end(), [&](auto&& x) { return x.id == hash; }); if (opos == objmap.end()) { - LOG(error) << "No object list found for task " << taskname << " (hash=" << hash << ")"; + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "No object list found for task %{public}s (hash=%d).", + taskname.c_str(), hash); return; } auto objects = opos->bindings; - if (std::find(objects.begin(), objects.end(), obj.name) == objects.end()) { - LOG(error) << "No object " << obj.name << " in map for task " << taskname; + if (std::find(objects.begin(), objects.end(), obj.container) == objects.end()) { + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "mergePart", "No container %{public}s in map for task %{public}s.", + obj.container.c_str(), taskname.c_str()); return; } auto nameHash = runtime_hash(obj.name.c_str()); InputObjectRoute key{obj.name, nameHash, taskname, hash, policy, sourceType}; auto existing = std::find_if(inputObjects->begin(), inputObjects->end(), [&](auto&& x) { return (x.first.uniqueId == nameHash) && (x.first.taskHash == hash); }); // If it's the first one, we just add it to the list. + O2_SIGNPOST_START(histogram_registry, did, "merging", "Starting merging of %{public}s/%{public}s/%d", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); if (existing == inputObjects->end()) { obj.count = objh->mPipelineSize; - inputObjects->push_back(std::make_pair(key, obj)); + inputObjects->emplace_back(key, obj); existing = inputObjects->end() - 1; } else { obj.count = existing->second.count; // Otherwise, we merge it with the existing one. auto merger = existing->second.kind->GetMerge(); if (!merger) { - LOG(error) << "Already one unmergeable object found for " << obj.name; + O2_SIGNPOST_END(histogram_registry, did, "merging", "Unabled to merge"); + O2_SIGNPOST_END_WITH_ERROR(histogram_registry, hid, "merging", "Already one unmergeable object found for %{public}s", obj.name.c_str()); return; } TList coll; @@ -350,15 +388,22 @@ AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& ctx) existing->second.count -= 1; if (existing->second.count != 0) { + O2_SIGNPOST_END(histogram_registry, did, "merging", "Done partial merging."); + O2_SIGNPOST_END(histogram_registry, hid, "mergePart", "Pipeline lanes still missing."); return; } + O2_SIGNPOST_END(histogram_registry, did, "merging", "Done merging."); // Write the object here. auto route = existing->first; auto entry = existing->second; auto file = ROOTfileNames.find(route.policy); if (file == ROOTfileNames.end()) { + O2_SIGNPOST_END(histogram_registry, hid, "mergePart", "Not matching any file."); return; } + O2_SIGNPOST_START(histogram_registry, did, "writing", "Starting writing of %{public}s/%{public}s/%d", + datah->dataOrigin.as().c_str(), datah->dataDescription.as().c_str(), + datah->subSpecification); auto filename = file->second; if (f[route.policy] == nullptr) { f[route.policy] = TFile::Open(filename.c_str(), "RECREATE"); @@ -372,47 +417,63 @@ AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& ctx) currentFile = filename; } - // translate the list-structure created by the registry into a directory structure within the file - std::function writeListToFile; - writeListToFile = [&](TList* list, TDirectory* parentDir) { - TIter next(list); - TObject* object = nullptr; - while ((object = next())) { - if (object->InheritsFrom(TList::Class())) { - writeListToFile(static_cast(object), parentDir->mkdir(object->GetName(), object->GetName(), true)); + // FIXME: handle folders + f[route.policy]->cd("/"); + auto* currentDir = f[route.policy]->GetDirectory(currentDirectory.c_str()); + + // In case we need a folder for the registry, let's create it. + if (folderForContainer) { + auto* histogramRegistryFolder = currentDir->GetDirectory(obj.container.data()); + if (!histogramRegistryFolder) { + histogramRegistryFolder = currentDir->mkdir(obj.container.c_str(), "", kTRUE); + } + currentDir = histogramRegistryFolder; + } + + // The name contains a path... + int objSize = 0; + if (sourceType == HistogramRegistrySource) { + TDirectory* currentFolder = currentDir; + O2_SIGNPOST_EVENT_EMIT(histogram_registry, hid, "mergePart", "Toplevel folder is %{public}s.", + currentDir->GetName()); + std::string objName = entry.name; + auto lastSlash = entry.name.rfind('/'); + + if (lastSlash != std::string::npos) { + auto dirname = entry.name.substr(0, lastSlash); + objName = entry.name.substr(lastSlash + 1); + currentFolder = currentDir->GetDirectory(dirname.c_str()); + if (!currentFolder) { + O2_SIGNPOST_EVENT_EMIT(histogram_registry, hid, "mergePart", "Creating folder %{public}s", + dirname.c_str()); + currentFolder = currentDir->mkdir(dirname.c_str(), "", kTRUE); } else { - parentDir->WriteObjectAny(object, object->Class(), object->GetName()); - auto* written = list->Remove(object); - delete written; + O2_SIGNPOST_EVENT_EMIT(histogram_registry, hid, "mergePart", "Folder %{public}s already there.", + currentFolder->GetName()); } } - }; - - TDirectory* currentDir = f[route.policy]->GetDirectory(currentDirectory.c_str()); - if (route.sourceType == OutputObjSourceType::HistogramRegistrySource) { - auto* outputList = static_cast(entry.obj); - outputList->SetOwner(false); - - // if registry should live in dedicated folder a TNamed object is appended to the list - if (outputList->Last() && outputList->Last()->IsA() == TNamed::Class()) { - delete outputList->Last(); - outputList->RemoveLast(); - currentDir = currentDir->mkdir(outputList->GetName(), outputList->GetName(), true); - } - - writeListToFile(outputList, currentDir); - outputList->SetOwner(); - delete outputList; + O2_SIGNPOST_EVENT_EMIT(histogram_registry, hid, "mergePart", "Writing %{public}s of kind %{public}s in %{public}s", + entry.name.c_str(), entry.kind->GetName(), currentDir->GetName()); + objSize = currentFolder->WriteObjectAny(entry.obj, entry.kind, objName.c_str()); + O2_SIGNPOST_END(histogram_registry, did, "writing", "End writing %{public}s", entry.name.c_str()); + delete (TObject*)entry.obj; entry.obj = nullptr; } else { - currentDir->WriteObjectAny(entry.obj, entry.kind, entry.name.c_str()); + O2_SIGNPOST_EVENT_EMIT(histogram_registry, hid, "mergePart", "Writing %{public}s of kind %{public}s in %{public}s", + entry.name.c_str(), entry.kind->GetName(), currentDir->GetName()); + objSize = currentDir->WriteObjectAny(entry.obj, entry.kind, entry.name.c_str()); + O2_SIGNPOST_END(histogram_registry, did, "writing", "End writing %{public}s", entry.name.c_str()); delete (TObject*)entry.obj; entry.obj = nullptr; } + O2_SIGNPOST_END(histogram_registry, hid, "mergePart", "Done merging object of %d bytes.", objSize); }; - for (int pi = 0; pi < pc.inputs().getNofParts(0); ++pi) { + O2_SIGNPOST_ID_GENERATE(rid, histogram_registry); + O2_SIGNPOST_START(histogram_registry, rid, "processParts", "Start merging %zu parts received together.", pc.inputs().getNofParts(0)); + for (auto pi = 0U; pi < pc.inputs().getNofParts(0); ++pi) { mergePart(pc.inputs().get("x", pi)); } + O2_SIGNPOST_END(histogram_registry, rid, "processParts", "Done histograms in multipart message."); }; }}; } diff --git a/Framework/AnalysisSupport/src/DataInputDirector.cxx b/Framework/AnalysisSupport/src/DataInputDirector.cxx index 7cc0134a27968..46674f19400a6 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.cxx +++ b/Framework/AnalysisSupport/src/DataInputDirector.cxx @@ -16,8 +16,8 @@ #include "Framework/AnalysisDataModelHelpers.h" #include "Framework/Output.h" #include "Framework/Signpost.h" +#include "Framework/FragmentToBatch.h" #include "Headers/DataHeader.h" -#include "Framework/TableTreeHelpers.h" #include "Monitoring/Tags.h" #include "Monitoring/Metric.h" #include "Monitoring/Monitoring.h" @@ -57,12 +57,10 @@ FileNameHolder* makeFileNameHolder(std::string fileName) return fileNameHolder; } -DataInputDescriptor::DataInputDescriptor(bool alienSupport, int level, o2::monitoring::Monitoring* monitoring, int allowedParentLevel, std::string parentFileReplacement) +DataInputDescriptor::DataInputDescriptor(bool alienSupport, int level, DataInputDirectorContext& context) : mAlienSupport(alienSupport), - mMonitoring(monitoring), - mAllowedParentLevel(allowedParentLevel), - mParentFileReplacement(std::move(parentFileReplacement)), - mLevel(level) + mLevel(level), + mContext(context) { std::vector capabilitiesSpecs = { "O2Framework:RNTupleObjectReadingCapability", @@ -124,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()) { @@ -135,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)); } @@ -148,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)); } @@ -157,13 +169,13 @@ bool DataInputDescriptor::setFile(int counter, std::string_view origin) // get the parent file map if exists mParentFileMap = (TMap*)rootFS->GetFile()->Get("parentFiles"); // folder name (DF_XXX) --> parent file (absolute path) - if (mParentFileMap && !mParentFileReplacement.empty()) { - auto pos = mParentFileReplacement.find(';'); + if (mParentFileMap && !mContext.parentFileReplacement.empty()) { + auto pos = mContext.parentFileReplacement.find(';'); if (pos == std::string::npos) { - throw std::runtime_error(fmt::format("Invalid syntax in aod-parent-base-path-replacement: \"{}\"", mParentFileReplacement.c_str())); + throw std::runtime_error(fmt::format("Invalid syntax in aod-parent-base-path-replacement: \"{}\"", mContext.parentFileReplacement.c_str())); } - auto from = mParentFileReplacement.substr(0, pos); - auto to = mParentFileReplacement.substr(pos + 1); + auto from = mContext.parentFileReplacement.substr(0, pos); + auto to = mContext.parentFileReplacement.substr(pos + 1); auto it = mParentFileMap->MakeIterator(); while (auto obj = it->Next()) { @@ -220,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; } @@ -236,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 {}; } @@ -253,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 @@ -280,17 +314,17 @@ DataInputDescriptor* DataInputDescriptor::getParentFile(int counter, int numTF, } } - if (mLevel == mAllowedParentLevel) { - throw std::runtime_error(fmt::format(R"(while looking for tree "{}", the parent file was requested but we are already at level {} of maximal allowed level {} for DF "{}" in file "{}")", treename.c_str(), mLevel, mAllowedParentLevel, folderName.c_str(), + if (mLevel == mContext.allowedParentLevel) { + throw std::runtime_error(fmt::format(R"(while looking for tree "{}", the parent file was requested but we are already at level {} of maximal allowed level {} for DF "{}" in file "{}")", treename.c_str(), mLevel, mContext.allowedParentLevel, folderName.c_str(), rootFS->GetFile()->GetName())); } LOGP(info, "Opening parent file {} for DF {}", parentFileName->GetString().Data(), folderName.c_str()); - mParentFile = new DataInputDescriptor(mAlienSupport, mLevel + 1, mMonitoring, mAllowedParentLevel, mParentFileReplacement); + mParentFile = new DataInputDescriptor(mAlienSupport, mLevel + 1, mContext); 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; } @@ -316,7 +350,9 @@ void DataInputDescriptor::printFileOpening() monitoringInfo += fmt::format(",se={},open_time={:.1f}", alienFile->GetSE(), alienFile->GetElapsed()); } #endif - mMonitoring->send(o2::monitoring::Metric{monitoringInfo, "aod-file-open-info"}.addTag(o2::monitoring::tags::Key::Subsystem, o2::monitoring::tags::Value::DPL)); + if (mContext.monitoring) { + mContext.monitoring->send(o2::monitoring::Metric{monitoringInfo, "aod-file-open-info"}.addTag(o2::monitoring::tags::Key::Subsystem, o2::monitoring::tags::Value::DPL)); + } LOGP(info, "Opening file: {}", monitoringInfo); } @@ -337,7 +373,9 @@ void DataInputDescriptor::printFileStatistics() monitoringInfo += fmt::format(",se={},open_time={:.1f}", alienFile->GetSE(), alienFile->GetElapsed()); } #endif - mMonitoring->send(o2::monitoring::Metric{monitoringInfo, "aod-file-read-info"}.addTag(o2::monitoring::tags::Key::Subsystem, o2::monitoring::tags::Value::DPL)); + if (mContext.monitoring) { + mContext.monitoring->send(o2::monitoring::Metric{monitoringInfo, "aod-file-read-info"}.addTag(o2::monitoring::tags::Key::Subsystem, o2::monitoring::tags::Value::DPL)); + } LOGP(info, "Read info: {}", monitoringInfo); } @@ -448,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; @@ -482,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) { @@ -524,27 +580,15 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh return true; } -DataInputDirector::DataInputDirector() -{ - createDefaultDataInputDescriptor(); -} - -DataInputDirector::DataInputDirector(std::string inputFile, o2::monitoring::Monitoring* monitoring, int allowedParentLevel, std::string parentFileReplacement) : mMonitoring(monitoring), mAllowedParentLevel(allowedParentLevel), mParentFileReplacement(std::move(parentFileReplacement)) +DataInputDirector::DataInputDirector(std::vector inputFiles, DataInputDirectorContext&& context) + : mContext{context} { - if (inputFile.size() && inputFile[0] == '@') { - inputFile.erase(0, 1); - setInputfilesFile(inputFile); + if (inputFiles.size() == 1 && !inputFiles[0].empty() && inputFiles[0][0] == '@') { + setInputfilesFile(inputFiles.back().substr(1, -1)); } else { - mdefaultInputFiles.emplace_back(makeFileNameHolder(inputFile)); - } - - createDefaultDataInputDescriptor(); -} - -DataInputDirector::DataInputDirector(std::vector inputFiles, o2::monitoring::Monitoring* monitoring, int allowedParentLevel, std::string parentFileReplacement) : mMonitoring(monitoring), mAllowedParentLevel(allowedParentLevel), mParentFileReplacement(std::move(parentFileReplacement)) -{ - for (auto inputFile : inputFiles) { - mdefaultInputFiles.emplace_back(makeFileNameHolder(inputFile)); + for (auto inputFile : inputFiles) { + mdefaultInputFiles.emplace_back(makeFileNameHolder(inputFile)); + } } createDefaultDataInputDescriptor(); @@ -576,7 +620,7 @@ void DataInputDirector::createDefaultDataInputDescriptor() if (mdefaultDataInputDescriptor) { delete mdefaultDataInputDescriptor; } - mdefaultDataInputDescriptor = new DataInputDescriptor(mAlienSupport, 0, mMonitoring, mAllowedParentLevel, mParentFileReplacement); + mdefaultDataInputDescriptor = new DataInputDescriptor(mAlienSupport, 0, mContext); mdefaultDataInputDescriptor->setInputfilesFile(minputfilesFile); mdefaultDataInputDescriptor->setFilenamesRegex(mFilenameRegex); @@ -700,7 +744,7 @@ bool DataInputDirector::readJsonDocument(Document* jsonDoc) return false; } // create a new dataInputDescriptor - auto didesc = new DataInputDescriptor(mAlienSupport, 0, mMonitoring, mAllowedParentLevel, mParentFileReplacement); + auto didesc = new DataInputDescriptor(mAlienSupport, 0, mContext); didesc->setDefaultInputfiles(&mdefaultInputFiles); itemName = "table"; @@ -827,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) @@ -850,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 61b477bd8522d..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 @@ -37,8 +38,29 @@ struct FileNameHolder { std::vector listOfTimeFrameNumbers; std::vector alreadyRead; }; + FileNameHolder* makeFileNameHolder(std::string fileName); +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 { /// Holds information concerning the reading of an aod table. @@ -50,7 +72,7 @@ class DataInputDescriptor std::string treename = ""; std::unique_ptr matcher; - DataInputDescriptor(bool alienSupport, int level, o2::monitoring::Monitoring* monitoring = nullptr, int allowedParentLevel = 0, std::string parentFileReplacement = ""); + DataInputDescriptor(bool alienSupport, int level, DataInputDirectorContext& context); void printOut(); @@ -64,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(); @@ -74,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); @@ -93,16 +118,13 @@ class DataInputDescriptor std::string* minputfilesFilePtr = nullptr; std::string mFilenameRegex = ""; std::string* mFilenameRegexPtr = nullptr; - int mAllowedParentLevel = 0; - std::string mParentFileReplacement; std::vector mfilenames; std::vector* mdefaultFilenamesPtr = nullptr; std::shared_ptr mCurrentFilesystem; int mCurrentFileID = -1; bool mAlienSupport = false; - o2::monitoring::Monitoring* mMonitoring = nullptr; - + DataInputDirectorContext& mContext; TMap* mParentFileMap = nullptr; DataInputDescriptor* mParentFile = nullptr; int mLevel = 0; // level of parent files @@ -120,9 +142,7 @@ class DataInputDirector /// and the related input files public: - DataInputDirector(); - DataInputDirector(std::string inputFile, o2::monitoring::Monitoring* monitoring = nullptr, int allowedParentLevel = 0, std::string parentFileReplacement = ""); - DataInputDirector(std::vector inputFiles, o2::monitoring::Monitoring* monitoring = nullptr, int allowedParentLevel = 0, std::string parentFileReplacement = ""); + DataInputDirector(std::vector inputFiles, DataInputDirectorContext&& context); ~DataInputDirector(); void reset(); @@ -149,18 +169,15 @@ class DataInputDirector uint64_t getTotalSizeUncompressed(); private: + DataInputDirectorContext mContext; std::string minputfilesFile; std::string* const minputfilesFilePtr = &minputfilesFile; std::string mFilenameRegex; - int mAllowedParentLevel = 0; - std::string mParentFileReplacement; std::string* const mFilenameRegexPtr = &mFilenameRegex; DataInputDescriptor* mdefaultDataInputDescriptor = nullptr; std::vector mdefaultInputFiles; std::vector mdataInputDescriptors; - o2::monitoring::Monitoring* mMonitoring = nullptr; - bool mDebugMode = false; bool mAlienSupport = false; diff --git a/Framework/AnalysisSupport/src/OnDemandPlugin.cxx b/Framework/AnalysisSupport/src/OnDemandPlugin.cxx new file mode 100644 index 0000000000000..9438f9bf69c96 --- /dev/null +++ b/Framework/AnalysisSupport/src/OnDemandPlugin.cxx @@ -0,0 +1,32 @@ +// Copyright 2019-2025 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/Plugins.h" +#include "Framework/AlgorithmSpec.h" +#include "AODReaderHelpers.h" + +struct ExtendedTableSpawner : o2::framework::AlgorithmPlugin { + o2::framework::AlgorithmSpec create(o2::framework::ConfigContext const& config) override + { + return o2::framework::readers::AODReaderHelpers::aodSpawnerCallback(config); + } +}; + +struct IndexTableBuilder : o2::framework::AlgorithmPlugin { + o2::framework::AlgorithmSpec create(o2::framework::ConfigContext const& config) override + { + return o2::framework::readers::AODReaderHelpers::indexBuilderCallback(config); + } +}; + +DEFINE_DPL_PLUGINS_BEGIN +DEFINE_DPL_PLUGIN_INSTANCE(ExtendedTableSpawner, CustomAlgorithm); +DEFINE_DPL_PLUGIN_INSTANCE(IndexTableBuilder, CustomAlgorithm); +DEFINE_DPL_PLUGINS_END diff --git a/Framework/AnalysisSupport/src/Plugin.cxx b/Framework/AnalysisSupport/src/Plugin.cxx index e39e76f01dbdd..5f61a236cbd58 100644 --- a/Framework/AnalysisSupport/src/Plugin.cxx +++ b/Framework/AnalysisSupport/src/Plugin.cxx @@ -27,6 +27,12 @@ O2_DECLARE_DYNAMIC_LOG(analysis_support); +struct ROOTTypeInfo { + EDataType type; + char suffix[3]; + int size; +}; + struct ROOTFileReader : o2::framework::AlgorithmPlugin { o2::framework::AlgorithmSpec create(o2::framework::ConfigContext const& config) override { 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/Core/src/TableTreeHelpers.cxx b/Framework/AnalysisSupport/src/TableTreeHelpers.cxx similarity index 93% rename from Framework/Core/src/TableTreeHelpers.cxx rename to Framework/AnalysisSupport/src/TableTreeHelpers.cxx index 92231cb9ce069..4cf3d3fce2266 100644 --- a/Framework/Core/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 @@ -296,31 +295,4 @@ struct BranchInfo { }; } // namespace -FragmentToBatch::FragmentToBatch(StreamerCreator creator, std::shared_ptr fragment, arrow::MemoryPool* pool) - : mFragment{std::move(fragment)}, - mArrowMemoryPool{pool}, - mCreator{std::move(creator)} -{ -} - -void FragmentToBatch::setLabel(const char* label) -{ - mTableLabel = label; -} - -void FragmentToBatch::fill(std::shared_ptr schema, std::shared_ptr format) -{ - auto options = std::make_shared(); - options->dataset_schema = schema; - auto scanner = format->ScanBatchesAsync(options, mFragment); - auto batch = (*scanner)(); - mRecordBatch = *batch.result(); - // Notice that up to here the buffer was not yet filled. -} - -std::shared_ptr FragmentToBatch::finalize() -{ - return mRecordBatch; -} - } // namespace o2::framework diff --git a/Framework/Core/test/benchmark_TableToTree.cxx b/Framework/AnalysisSupport/test/benchmark_TableToTree.cxx similarity index 100% rename from Framework/Core/test/benchmark_TableToTree.cxx rename to Framework/AnalysisSupport/test/benchmark_TableToTree.cxx diff --git a/Framework/AnalysisSupport/test/test_DataInputDirector.cxx b/Framework/AnalysisSupport/test/test_DataInputDirector.cxx index cb49ccb83b0b7..6ccaf3a92c0e1 100644 --- a/Framework/AnalysisSupport/test/test_DataInputDirector.cxx +++ b/Framework/AnalysisSupport/test/test_DataInputDirector.cxx @@ -50,7 +50,7 @@ BOOST_AUTO_TEST_CASE(TestDatainputDirector) jf << R"(})" << std::endl; jf.close(); - DataInputDirector didir1; + DataInputDirector didir1({}, {}); BOOST_CHECK(didir1.readJson(jsonFile)); didir1.printOut(); printf("\n\n"); @@ -60,8 +60,8 @@ BOOST_AUTO_TEST_CASE(TestDatainputDirector) auto dh = DataHeader(DataDescription{"DUE"}, DataOrigin{"AOD"}, DataHeader::SubSpecificationType{0}); - //auto [file1, directory1] = didir1.getFileFolder(dh, 1, 0); - //BOOST_CHECK_EQUAL(file1->GetName(), "Bresults_1.root"); + // auto [file1, directory1] = didir1.getFileFolder(dh, 1, 0); + // BOOST_CHECK_EQUAL(file1->GetName(), "Bresults_1.root"); auto didesc = didir1.getDataInputDescriptor(dh); BOOST_CHECK(didesc); @@ -96,13 +96,13 @@ BOOST_AUTO_TEST_CASE(TestDatainputDirector) "Aresults_2.root", "Bresults_1.root", "Bresults_2.root"}; - DataInputDirector didir2(inputFiles); + DataInputDirector didir2(inputFiles, {}); didir2.printOut(); printf("\n\n"); BOOST_CHECK(didir2.readJson(jsonFile)); - //auto [file2, directory2] = didir2.getFileFolder(dh, 1, 0); - //BOOST_CHECK_EQUAL(file2->GetName(), "Bresults_1.root"); + // auto [file2, directory2] = didir2.getFileFolder(dh, 1, 0); + // BOOST_CHECK_EQUAL(file2->GetName(), "Bresults_1.root"); didesc = didir2.getDataInputDescriptor(dh); BOOST_CHECK(didesc); 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 aba1f3ed4e13d..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" @@ -18,9 +19,9 @@ #include "Framework/RawDeviceService.h" #include "Framework/Output.h" #include "Framework/Signpost.h" -#include "Framework/AnalysisContext.h" -#include "Framework/ConfigContext.h" +#include "Framework/DanglingEdgesContext.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); @@ -67,55 +67,71 @@ void fillValidRoutes(CCDBFetcherHelper& helper, std::vector(); - std::vector> schemas; - auto schemaMetadata = std::make_shared(); - - for (auto& input : ac.analysisCCDBInputs) { - 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); - continue; + 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)); } - // Ignore the non ccdb: entries - if (!m.name.starts_with("ccdb:")) { - continue; + } + std::vector> schemas; + 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) { + 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; + } + if (!m.name.starts_with("ccdb:")) { + continue; + } + 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, fieldMetadata)); } - // Create the schema of the output - auto metadata = std::make_shared(); - metadata->Append("url", m.defaultValue.asString()); - auto columnName = m.name.substr(strlen("ccdb:")); - fields.emplace_back(std::make_shared(columnName, arrow::binary_view(), false, metadata)); + schemas.emplace_back(std::make_shared(fields, schemaMetadata)); } - schemas.emplace_back(std::make_shared(fields, schemaMetadata)); - } - return adaptStateful([schemas](CallbackService& callbacks, ConfigParamRegistry const& options, DeviceSpec const& spec) { + std::shared_ptr helper = std::make_shared(); CCDBFetcherHelper::initialiseHelper(*helper, options); 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) { std::vector ops; auto inputBinding = *schema->metadata()->Get("sourceTable"); + auto inputMatcher = DataSpecUtils::fromString(*schema->metadata()->Get("sourceMatcher")); auto outRouteDesc = *schema->metadata()->Get("outputRoute"); std::string outBinding = *schema->metadata()->Get("outputBinding"); O2_SIGNPOST_EVENT_EMIT_INFO(ccdb, sid, "fetchFromAnalysisCCDB", "Fetching CCDB objects for %{public}s's columns with timestamps from %{public}s and putting them in route %{public}s", outBinding.c_str(), inputBinding.c_str(), outRouteDesc.c_str()); - auto ref = inputs.get(inputBinding); - auto table = ref->asArrowTable(); + auto table = inputs.get(inputMatcher)->asArrowTable(); // FIXME: make the fTimestamp column configurable. auto timestampColumn = table->GetColumnByName("fTimestamp"); O2_SIGNPOST_EVENT_EMIT_INFO(ccdb, sid, "fetchFromAnalysisCCDB", @@ -128,11 +144,11 @@ AlgorithmSpec AnalysisCCDBHelpers::fetchFromCCDB(ConfigContext const& ctx) int outputRouteIndex = bindings.at(outRouteDesc); auto& spec = helper->routes[outputRouteIndex].matcher; std::vector> builders; - for (auto& _ : schema->fields()) { + for (auto const& _ : schema->fields()) { builders.emplace_back(std::make_shared()); } - for (size_t ci = 0; ci < timestampColumn->num_chunks(); ++ci) { + for (auto ci = 0; ci < timestampColumn->num_chunks(); ++ci) { std::shared_ptr chunk = timestampColumn->chunk(ci); auto const* timestamps = chunk->data()->GetValuesSafe(1); @@ -180,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/AnalysisCCDBHelpers.h b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.h index f8175034da0ba..3be2138bd2b5c 100644 --- a/Framework/CCDBSupport/src/AnalysisCCDBHelpers.h +++ b/Framework/CCDBSupport/src/AnalysisCCDBHelpers.h @@ -17,7 +17,7 @@ namespace o2::framework { struct AnalysisCCDBHelpers { - static AlgorithmSpec fetchFromCCDB(ConfigContext const& ctx); + static AlgorithmSpec fetchFromCCDB(ConfigContext const&); }; } // namespace o2::framework diff --git a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx index 14c3fefb31024..8d50dac63a67b 100644 --- a/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx +++ b/Framework/CCDBSupport/src/CCDBFetcherHelper.cxx @@ -51,10 +51,20 @@ void CCDBFetcherHelper::initialiseHelper(CCDBFetcherHelper& helper, ConfigParamR auto defHost = options.get("condition-backend"); auto checkRate = options.get("condition-tf-per-query"); auto checkMult = options.get("condition-tf-per-query-multiplier"); + helper.useTFSlice = options.get("condition-use-slice-for-prescaling"); helper.timeToleranceMS = options.get("condition-time-tolerance"); helper.queryPeriodGlo = checkRate > 0 ? checkRate : std::numeric_limits::max(); - helper.queryPeriodFactor = checkMult > 0 ? checkMult : 1; - LOGP(info, "CCDB Backend at: {}, validity check for every {} TF{}", defHost, helper.queryPeriodGlo, helper.queryPeriodFactor == 1 ? std::string{} : fmt::format(", (query for high-rate objects downscaled by {})", helper.queryPeriodFactor)); + helper.queryPeriodFactor = checkMult == 0 ? 1 : checkMult; + std::string extraCond{}; + if (helper.useTFSlice) { + extraCond = ". Use TFSlice"; + if (helper.useTFSlice > 0) { + extraCond += fmt::format(" + max TFcounter jump <= {}", helper.useTFSlice); + } + } + LOGP(info, "CCDB Backend at: {}, validity check for every {} TF{}{}", defHost, helper.queryPeriodGlo, + helper.queryPeriodFactor == 1 ? std::string{} : (helper.queryPeriodFactor > 0 ? fmt::format(", (query for high-rate objects downscaled by {})", helper.queryPeriodFactor) : fmt::format(", (query downscaled as TFcounter%{})", -helper.queryPeriodFactor)), + extraCond); LOGP(info, "Hook to enable signposts for CCDB messages at {}", (void*)&private_o2_log_ccdb->stacktrace); auto remapString = options.get("condition-remap"); ParserResult result = parseRemappings(remapString.c_str()); @@ -205,12 +215,21 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con // If timestamp is before the time the element was cached or after the claimed validity, we need to check validity, again // when online. bool cacheExpired = (validUntil <= timestampToUse) || (op.timestamp < cachePopulatedAt); - checkValidity = (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) && (isOnline || cacheExpired); + if (isOnline || cacheExpired) { + if (!helper->useTFSlice) { + checkValidity = chRate > 0 ? (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) : (timingInfo.tfCounter % -chRate) == 0; + } else { + checkValidity = chRate > 0 ? (std::abs(int(timingInfo.timeslice - url2uuid->second.lastCheckedSlice)) >= chRate) : (timingInfo.timeslice % -chRate) == 0; + if (!checkValidity && helper->useTFSlice > std::abs(chRate)) { // make sure the interval is tolerated unless the check rate itself is too large + checkValidity = std::abs(int(timingInfo.tfCounter) - url2uuid->second.lastCheckedTF) > helper->useTFSlice; + } + } + } } else { checkValidity = true; // never skip check if the cache is empty } - O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "checkValidity is %{public}s for tfID %d of %{public}s", checkValidity ? "true" : "false", timingInfo.tfCounter, path.data()); + O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "checkValidity is %{public}s for tf%{public}s %zu of %{public}s", checkValidity ? "true" : "false", helper->useTFSlice ? "ID" : "Slice", helper->useTFSlice ? timingInfo.timeslice : timingInfo.tfCounter, path.data()); const auto& api = helper->getAPI(path); if (checkValidity && (!api.isSnapshotMode() || etag.empty())) { // in the snapshot mode the object needs to be fetched only once @@ -226,6 +245,7 @@ auto CCDBFetcherHelper::populateCacheWith(std::shared_ptr con LOGP(detail, "******** Default entry used for {} ********", path); } helper->mapURL2UUID[path].lastCheckedTF = timingInfo.tfCounter; + helper->mapURL2UUID[path].lastCheckedSlice = timingInfo.timeslice; if (etag.empty()) { helper->mapURL2UUID[path].etag = headers["ETag"]; // update uuid helper->mapURL2UUID[path].cachePopulatedAt = timestampToUse; @@ -234,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; @@ -251,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 e3453b48bf156..a6cb3f70215af 100644 --- a/Framework/CCDBSupport/src/CCDBFetcherHelper.h +++ b/Framework/CCDBSupport/src/CCDBFetcherHelper.h @@ -33,6 +33,7 @@ struct CCDBFetcherHelper { size_t minSize = -1ULL; size_t maxSize = 0; int lastCheckedTF = 0; + int lastCheckedSlice = 0; }; struct RemapMatcher { @@ -83,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"; @@ -94,6 +97,7 @@ struct CCDBFetcherHelper { int queryPeriodGlo = 1; int queryPeriodFactor = 1; int64_t timeToleranceMS = 5000; + int useTFSlice = 0; // if non-zero, use TFslice instead of TFcounter for the validity check. If > requested checking rate, add additional check on |lastTFchecked - TCcounter|<=useTFSlice o2::ccdb::CcdbApi& getAPI(const std::string& path); static void initialiseHelper(CCDBFetcherHelper& helper, ConfigParamRegistry const& options); diff --git a/Framework/CCDBSupport/src/CCDBHelpers.cxx b/Framework/CCDBSupport/src/CCDBHelpers.cxx index acf8b782f8f06..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,9 +38,11 @@ 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; + int lastCheckedSlice = 0; }; struct RemapMatcher { @@ -49,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"; @@ -60,6 +66,7 @@ struct CCDBFetcherHelper { int queryPeriodGlo = 1; int queryPeriodFactor = 1; int64_t timeToleranceMS = 5000; + int useTFSlice = 0; // if non-zero, use TFslice instead of TFcounter for the validity check. If > requested checking rate, add additional check on |lastTFchecked - TCcounter|<=useTFSlice o2::ccdb::CcdbApi& getAPI(const std::string& path) { @@ -78,7 +85,7 @@ struct CCDBFetcherHelper { return apis[entry == remappings.end() ? "" : entry->second]; } }; -} +} // namespace bool isPrefix(std::string_view prefix, std::string_view full) { @@ -165,10 +172,20 @@ void initialiseHelper(CCDBFetcherHelper& helper, ConfigParamRegistry const& opti auto defHost = options.get("condition-backend"); auto checkRate = options.get("condition-tf-per-query"); auto checkMult = options.get("condition-tf-per-query-multiplier"); + helper.useTFSlice = options.get("condition-use-slice-for-prescaling"); helper.timeToleranceMS = options.get("condition-time-tolerance"); helper.queryPeriodGlo = checkRate > 0 ? checkRate : std::numeric_limits::max(); - helper.queryPeriodFactor = checkMult > 0 ? checkMult : 1; - LOGP(info, "CCDB Backend at: {}, validity check for every {} TF{}", defHost, helper.queryPeriodGlo, helper.queryPeriodFactor == 1 ? std::string{} : fmt::format(", (query for high-rate objects downscaled by {})", helper.queryPeriodFactor)); + helper.queryPeriodFactor = checkMult == 0 ? 1 : checkMult; + std::string extraCond{}; + if (helper.useTFSlice) { + extraCond = ". Use TFSlice"; + if (helper.useTFSlice > 0) { + extraCond += fmt::format(" + max TFcounter jump <= {}", helper.useTFSlice); + } + } + LOGP(info, "CCDB Backend at: {}, validity check for every {} TF{}{}", defHost, helper.queryPeriodGlo, + helper.queryPeriodFactor == 1 ? std::string{} : (helper.queryPeriodFactor > 0 ? fmt::format(", (query for high-rate objects downscaled by {})", helper.queryPeriodFactor) : fmt::format(", (query downscaled as TFcounter%{})", -helper.queryPeriodFactor)), + extraCond); LOGP(info, "Hook to enable signposts for CCDB messages at {}", (void*)&private_o2_log_ccdb->stacktrace); auto remapString = options.get("condition-remap"); CCDBHelpers::ParserResult result = CCDBHelpers::parseRemappings(remapString.c_str()); @@ -276,7 +293,7 @@ auto populateCacheWith(std::shared_ptr const& helper, O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "Adding metadata %{public}s: %{public}s to the request", key.data(), value.data()); metadata[key] = value; } else if (meta.name == "ccdb-query-rate") { - chRate = meta.defaultValue.get() * helper->queryPeriodFactor; + chRate = std::max(1, meta.defaultValue.get()) * helper->queryPeriodFactor; } } const auto url2uuid = helper->mapURL2UUID.find(path); @@ -289,12 +306,21 @@ auto populateCacheWith(std::shared_ptr const& helper, // If timestamp is before the time the element was cached or after the claimed validity, we need to check validity, again // when online. bool cacheExpired = (validUntil <= timestampToUse) || (timestamp < cachePopulatedAt); - checkValidity = (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) && (isOnline || cacheExpired); + if (isOnline || cacheExpired) { + if (!helper->useTFSlice) { + checkValidity = chRate > 0 ? (std::abs(int(timingInfo.tfCounter - url2uuid->second.lastCheckedTF)) >= chRate) : (timingInfo.tfCounter % -chRate) == 0; + } else { + checkValidity = chRate > 0 ? (std::abs(int(timingInfo.timeslice - url2uuid->second.lastCheckedSlice)) >= chRate) : (timingInfo.timeslice % -chRate) == 0; + if (!checkValidity && helper->useTFSlice > std::abs(chRate)) { // make sure the interval is tolerated unless the check rate itself is too large + checkValidity = std::abs(int(timingInfo.tfCounter) - url2uuid->second.lastCheckedTF) > helper->useTFSlice; + } + } + } } else { checkValidity = true; // never skip check if the cache is empty } - O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "checkValidity is %{public}s for tfID %d of %{public}s", checkValidity ? "true" : "false", timingInfo.tfCounter, path.data()); + O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "populateCacheWith", "checkValidity is %{public}s for tf%{public}s %zu of %{public}s", checkValidity ? "true" : "false", helper->useTFSlice ? "ID" : "Slice", helper->useTFSlice ? timingInfo.timeslice : timingInfo.tfCounter, path.data()); const auto& api = helper->getAPI(path); if (checkValidity && (!api.isSnapshotMode() || etag.empty())) { // in the snapshot mode the object needs to be fetched only once @@ -310,12 +336,16 @@ auto populateCacheWith(std::shared_ptr const& helper, LOGP(detail, "******** Default entry used for {} ********", path); } helper->mapURL2UUID[path].lastCheckedTF = timingInfo.tfCounter; + helper->mapURL2UUID[path].lastCheckedSlice = timingInfo.timeslice; if (etag.empty()) { 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; @@ -328,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; @@ -346,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? } @@ -360,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; @@ -382,21 +416,22 @@ AlgorithmSpec CCDBHelpers::fetchFromCCDB() std::map metadata; std::map headers; std::string etag; - bool checkValidity = std::abs(int(timingInfo.tfCounter - helper->lastCheckedTFCounterOrbReset)) >= helper->queryPeriodGlo; + int32_t counter = helper->useTFSlice ? timingInfo.timeslice : timingInfo.tfCounter; + bool checkValidity = std::abs(int(counter - helper->lastCheckedTFCounterOrbReset)) >= helper->queryPeriodGlo; const auto url2uuid = helper->mapURL2UUID.find(path); if (url2uuid != helper->mapURL2UUID.end()) { etag = url2uuid->second.etag; } else { checkValidity = true; // never skip check if the cache is empty } - O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "fetchFromCCDB", "checkValidity is %{public}s for tfID %d of %{public}s", - checkValidity ? "true" : "false", timingInfo.tfCounter, path.data()); + O2_SIGNPOST_EVENT_EMIT(ccdb, sid, "fetchFromCCDB", "checkValidity is %{public}s for tf%{public}s %d of %{public}s", + checkValidity ? "true" : "false", helper->useTFSlice ? "ID" : "Slice", counter, path.data()); Output output{"CTP", "OrbitReset", 0}; Long64_t newOrbitResetTime = orbitResetTime; auto&& v = allocator.makeVector(output); const auto& api = helper->getAPI(path); if (checkValidity && (!api.isSnapshotMode() || etag.empty())) { // in the snapshot mode the object needs to be fetched only once - helper->lastCheckedTFCounterOrbReset = timingInfo.tfCounter; + helper->lastCheckedTFCounterOrbReset = counter; api.loadFileToMemory(v, path, metadata, timingInfo.creation, &headers, etag, helper->createdNotAfter, helper->createdNotBefore); if ((headers.count("Error") != 0) || (etag.empty() && v.empty())) { LOGP(fatal, "Unable to find CCDB object {}/{}", path, timingInfo.creation); @@ -406,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); @@ -417,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); @@ -432,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) { @@ -457,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 936d8874179a5..45af3ad6c59cc 100644 --- a/Framework/Core/CMakeLists.txt +++ b/Framework/Core/CMakeLists.txt @@ -8,15 +8,14 @@ # 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(Framework - SOURCES src/AODReaderHelpers.cxx - src/AnalysisHelpers.cxx + SOURCES src/AnalysisHelpers.cxx src/AlgorithmSpec.cxx src/ArrowSupport.cxx src/ArrowTableSlicingCache.cxx src/AnalysisDataModel.cxx src/AnalysisSupportHelpers.cxx + src/AnalysisTask.cxx src/ASoA.cxx src/ASoAHelpers.cxx src/AsyncQueue.cxx @@ -87,6 +86,7 @@ o2_add_library(Framework src/FairMQDeviceProxy.cxx src/FairMQResizableBuffer.cxx src/FairOptionsRetriever.cxx + src/FragmentToBatch.cxx src/ConfigurationOptionsRetriever.cxx src/FreePortFinder.cxx src/GraphvizHelpers.cxx @@ -108,6 +108,7 @@ o2_add_library(Framework src/SimpleOptionsRetriever.cxx src/O2ControlHelpers.cxx src/O2ControlLabels.cxx + src/CommonLabels.cxx src/O2ControlParameters.cxx src/O2DataModelHelpers.cxx src/OutputSpec.cxx @@ -123,6 +124,7 @@ o2_add_library(Framework src/RootArrowFilesystem.cxx src/SendingPolicy.cxx src/ServiceRegistry.cxx + src/ServiceRegistryRef.cxx src/ServiceSpec.cxx src/SimpleResourceManager.cxx src/SimpleRawDeviceService.cxx @@ -131,7 +133,6 @@ o2_add_library(Framework src/TMessageSerializer.cxx src/TableBuilder.cxx src/TableConsumer.cxx - src/TableTreeHelpers.cxx src/TopologyPolicy.cxx src/TopologyPolicyHelpers.cxx src/TextDriverClient.cxx @@ -142,6 +143,8 @@ o2_add_library(Framework src/Array2D.cxx src/Variant.cxx src/VariantJSONHelpers.cxx + src/ExpressionJSONHelpers.cxx + src/IndexJSONHelpers.cxx src/VariantPropertyTreeHelpers.cxx src/WorkflowCustomizationHelpers.cxx src/WorkflowHelpers.cxx @@ -155,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 @@ -174,6 +178,7 @@ o2_add_library(Framework RapidJSON::RapidJSON Arrow::arrow_shared ArrowDataset::arrow_dataset_shared + $<$:ArrowCompute::arrow_compute_shared> Microsoft.GSL::GSL O2::FrameworkLogger Gandiva::gandiva_shared @@ -223,6 +228,7 @@ add_executable(o2-test-framework-core test/test_FairMQOptionsRetriever.cxx test/test_FairMQResizableBuffer.cxx test/test_FairMQ.cxx + test/test_ForwardInputs.cxx test/test_FrameworkDataFlowToDDS.cxx test/test_FrameworkDataFlowToO2Control.cxx test/test_Graphviz.cxx @@ -232,14 +238,17 @@ 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 test/test_Mermaid.cxx + test/test_MessageSet.cxx test/test_OptionsHelpers.cxx test/test_OverrideLabels.cxx test/test_O2DataModelHelpers.cxx test/test_RootConfigParamHelpers.cxx + test/test_ResourcesMonitoringHelpers.cxx test/test_Services.cxx test/test_StringHelpers.cxx test/test_StaticFor.cxx @@ -249,6 +258,7 @@ add_executable(o2-test-framework-core test/test_TimeParallelPipelining.cxx test/test_TimesliceIndex.cxx test/test_TypeTraits.cxx + test/test_TypeToTaskName.cxx test/test_TopologyPolicies.cxx test/test_Variants.cxx test/test_WorkflowHelpers.cxx @@ -348,7 +358,6 @@ foreach(b ASoAHelpers EventMixing HistogramRegistry - TableToTree ExternalFairMQDeviceProxies ) o2_add_executable(benchmark-${b} 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 4db774d88a224..fc17fa139875c 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -12,6 +12,7 @@ #ifndef O2_FRAMEWORK_ASOA_H_ #define O2_FRAMEWORK_ASOA_H_ +#include "Framework/ConcreteDataMatcher.h" #include "Framework/Pack.h" // IWYU pragma: export #include "Framework/FunctionalHelpers.h" // IWYU pragma: export #include "Headers/DataHeader.h" // IWYU pragma: export @@ -34,7 +35,6 @@ #include #include #include // IWYU pragma: export -#include namespace o2::framework { @@ -53,6 +53,12 @@ void dereferenceWithWrongType(const char* getter, const char* target); void missingFilterDeclaration(int hash, int ai); void notBoundTable(const char* tableName); void* extractCCDBPayload(char* payload, size_t size, TClass const* cl, const char* what); + +template +auto createFieldsFromColumns(framework::pack) +{ + return std::vector>{C::asArrowField()...}; +} } // namespace o2::soa namespace o2::soa @@ -212,6 +218,20 @@ using is_self_index_t = typename std::conditional_t, std namespace o2::aod { +namespace +{ +template map> +static consteval int getIndexPosToKey_impl() +{ + constexpr const auto pos = std::find(map.begin(), map.end(), true); + if constexpr (pos != map.end()) { + return std::distance(map.begin(), pos); + } else { + return -1; + } +} +} // namespace + /// Base type for table metadata template struct TableMetadata { @@ -238,15 +258,9 @@ struct TableMetadata { return getIndexPosToKey_impl(persistent_columns_t{})>(); } - template map> - static consteval int getIndexPosToKey_impl() + static std::shared_ptr getSchema() { - constexpr const auto pos = std::find(map.begin(), map.end(), true); - if constexpr (pos != map.end()) { - return std::distance(map.begin(), pos); - } else { - return -1; - } + return std::make_shared([](framework::pack&& p) { return o2::soa::createFieldsFromColumns(p); }(persistent_columns_t{})); } }; @@ -362,6 +376,12 @@ consteval const char* signature() return o2::aod::Hash::str; } +template +constexpr framework::ConcreteDataMatcher matcher() +{ + return {origin(), description(signature()), R.version}; +} + /// hash identification concepts template concept is_aod_hash = requires(T t) { t.hash; t.str; }; @@ -375,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 @@ -385,15 +419,15 @@ class Table; /// Type-checking index column binding struct Binding { void const* ptr = nullptr; - size_t hash = 0; - std::span refs; + uint32_t hash = 0; + // std::span refs; template void bind(T const* table) { ptr = table; hash = o2::framework::TypeIdHelpers::uniqueId(); - refs = std::span{T::originals}; + // refs = std::span{T::originals}; } template @@ -406,12 +440,6 @@ struct Binding { } }; -template -auto createFieldsFromColumns(framework::pack) -{ - return std::vector>{C::asArrowField()...}; -} - using SelectionVector = std::vector; template @@ -686,7 +714,7 @@ struct Column { static auto asArrowField() { - return std::make_shared(inherited_t::mLabel, framework::expressions::concreteArrowType(framework::expressions::selectArrowType())); + return std::make_shared(inherited_t::mLabel, soa::asArrowDataType()); } /// FIXME: rather than keeping this public we should have a protected @@ -1068,7 +1096,9 @@ struct TableIterator : IP, C... { : IP{policy}, C(columnData[framework::has_type_at_v(all_columns{})])... { - bind(); + if (this->size() != 0) { + bind(); + } } TableIterator(arrow::ChunkedArray* columnData[sizeof...(C)], IP&& policy) @@ -1076,7 +1106,9 @@ struct TableIterator : IP, C... { : IP{policy}, C(columnData[framework::has_type_at_v(all_columns{})])... { - bind(); + if (this->size() != 0) { + bind(); + } // In case we have an index column might need to constrain the actual // number of rows in the view to the range provided by the index. // FIXME: we should really understand what happens to an index when we @@ -1089,14 +1121,18 @@ struct TableIterator : IP, C... { : IP{static_cast(other)}, C(static_cast(other))... { - bind(); + if (this->size() != 0) { + bind(); + } } TableIterator& operator=(TableIterator other) { IP::operator=(static_cast(other)); (void(static_cast(*this) = static_cast(other)), ...); - bind(); + if (this->size() != 0) { + bind(); + } return *this; } @@ -1105,7 +1141,9 @@ struct TableIterator : IP, C... { : IP{static_cast(other)}, C(static_cast(other))... { - bind(); + if (this->size() != 0) { + bind(); + } } TableIterator& operator++() @@ -1152,12 +1190,6 @@ struct TableIterator : IP, C... { return *this; } - template - void doSetCurrentIndex(framework::pack, TA* current) - { - (CL::setCurrent(current), ...); - } - template auto getCurrent() const { @@ -1178,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 @@ -1259,7 +1302,9 @@ struct TableIterator : IP, C... { }; struct ArrowHelpers { + static std::shared_ptr joinTables(std::vector>&& tables); static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); + static std::shared_ptr joinTables(std::vector>&& tables, std::span labels); static std::shared_ptr concatTables(std::vector>&& tables); }; @@ -1267,6 +1312,9 @@ struct ArrowHelpers { template concept is_iterator = framework::base_of_template || framework::specialization_of_template; +template +concept is_table_or_iterator = is_table || is_iterator; + template concept with_originals = requires { T::originals.size(); @@ -1277,13 +1325,30 @@ 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(); }; template -concept with_base_table = not_void>::metadata::base_table_t>; +concept with_base_table = requires { + typename aod::MetadataTrait>::metadata::base_table_t; +}; + +template +concept with_expression_pack = requires { + typename T::expression_pack_t{}; +}; + +template +concept with_index_pack = requires { + typename T::index_pack_t{}; +}; template os1, size_t N2, std::array os2> consteval bool is_compatible() @@ -1338,15 +1403,24 @@ 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 static constexpr auto hasColumnForKey(framework::pack, std::string const& key) { - return ((C::inherited_t::mLabel == key) || ...); + auto caseInsensitiveCompare = [](const std::string_view& str1, const std::string& str2) { + return std::ranges::equal( + str1, str2, + [](char c1, char c2) { + return std::tolower(static_cast(c1)) == + std::tolower(static_cast(c2)); + }); + }; + return (caseInsensitiveCompare(C::inherited_t::mLabel, key) || ...); } template @@ -1355,6 +1429,12 @@ static constexpr std::pair hasKey(std::string const& key) return {hasColumnForKey(typename aod::MetadataTrait>::metadata::columns{}, key), aod::label()}; } +template +static constexpr std::pair hasKeyM(std::string const& key) +{ + return {hasColumnForKey(typename aod::MetadataTrait>::metadata::columns{}, key), aod::matcher()}; +} + template static constexpr auto haveKey(framework::pack, std::string const& key) { @@ -1389,6 +1469,31 @@ static constexpr std::string getLabelFromTypeForKey(std::string const& key) O2_BUILTIN_UNREACHABLE(); } +template +static constexpr framework::ConcreteDataMatcher getMatcherFromTypeForKey(std::string const& key) +{ + if constexpr (T::originals.size() == 1) { + auto locate = hasKeyM(key); + if (locate.first) { + return locate.second; + } + } else { + auto locate = [&](std::index_sequence) { + return std::vector{hasKeyM(key)...}; + }(std::make_index_sequence{}); + auto it = std::find_if(locate.begin(), locate.end(), [](auto const& x) { return x.first; }); + if (it != locate.end()) { + return it->second; + } + } + if constexpr (!OPT) { + notFoundColumn(getLabelFromType>().data(), key.data()); + } else { + return framework::ConcreteDataMatcher{header::DataOrigin{"AOD"}, header::DataDescription{"[MISSING]"}, 0}; + } + O2_BUILTIN_UNREACHABLE(); +} + template consteval static bool hasIndexTo(framework::pack&&) { @@ -1439,7 +1544,10 @@ struct PreslicePolicyGeneral : public PreslicePolicyBase { std::span getSliceFor(int value) const; }; -template +template +concept is_preslice_policy = std::derived_from; + +template struct PresliceBase : public Policy { constexpr static bool optional = OPT; using target_t = T; @@ -1447,7 +1555,7 @@ struct PresliceBase : public Policy { const std::string binding; PresliceBase(expressions::BindingNode index_) - : Policy{PreslicePolicyBase{{o2::soa::getLabelFromTypeForKey(std::string{index_.name})}, Entry(o2::soa::getLabelFromTypeForKey(std::string{index_.name}), std::string{index_.name})}, {}} + : Policy{PreslicePolicyBase{{o2::soa::getLabelFromTypeForKey(std::string{index_.name})}, Entry(o2::soa::getLabelFromTypeForKey(std::string{index_.name}), o2::soa::getMatcherFromTypeForKey(std::string{index_.name}), std::string{index_.name})}, {}} { } @@ -1482,7 +1590,11 @@ template using PresliceOptional = PresliceBase; template -concept is_preslice = std::derived_from; +concept is_preslice = std::derived_from&& + requires(T) +{ + T::optional; +}; /// Can be user to group together a number of Preslice declaration /// to avoid the limit of 100 data members per task @@ -1551,8 +1663,10 @@ auto doSliceBy(T const* table, o2::framework::PresliceBase const uint64_t offset = 0; auto out = container.getSliceFor(value, table->asArrowTable(), offset); auto t = typename T::self_t({out}, offset); - table->copyIndexBindings(t); - t.bindInternalIndicesTo(table); + if (t.tableSize() != 0) { + table->copyIndexBindings(t); + t.bindInternalIndicesTo(table); + } return t; } @@ -1560,9 +1674,11 @@ template auto doSliceByHelper(T const* table, std::span const& selection) { auto t = soa::Filtered({table->asArrowTable()}, selection); - table->copyIndexBindings(t); - t.bindInternalIndicesTo(table); - t.intersectWithSelection(table->getSelectedRows()); // intersect filters + if (t.tableSize() != 0) { + table->copyIndexBindings(t); + t.bindInternalIndicesTo(table); + t.intersectWithSelection(table->getSelectedRows()); // intersect filters + } return t; } @@ -1571,8 +1687,10 @@ template auto doSliceByHelper(T const* table, std::span const& selection) { auto t = soa::Filtered({table->asArrowTable()}, selection); - table->copyIndexBindings(t); - t.bindInternalIndicesTo(table); + if (t.tableSize() != 0) { + table->copyIndexBindings(t); + t.bindInternalIndicesTo(table); + } return t; } @@ -1596,12 +1714,16 @@ auto prepareFilteredSlice(T const* table, std::shared_ptr slice, u { if (offset >= static_cast(table->tableSize())) { Filtered fresult{{{slice}}, SelectionVector{}, 0}; - table->copyIndexBindings(fresult); + if (fresult.tableSize() != 0) { + table->copyIndexBindings(fresult); + } return fresult; } auto slicedSelection = sliceSelection(table->getSelectedRows(), slice->num_rows(), offset); Filtered fresult{{{slice}}, std::move(slicedSelection), offset}; - table->copyIndexBindings(fresult); + if (fresult.tableSize() != 0) { + table->copyIndexBindings(fresult); + } return fresult; } @@ -1619,37 +1741,43 @@ auto doFilteredSliceBy(T const* table, o2::framework::PresliceBase +template auto doSliceByCached(T const* table, framework::expressions::BindingNode const& node, int value, o2::framework::SliceCache& cache) { - auto localCache = cache.ptr->getCacheFor({o2::soa::getLabelFromTypeForKey(node.name), node.name}); + auto localCache = cache.ptr->getCacheFor({"", o2::soa::getMatcherFromTypeForKey(node.name), node.name}); auto [offset, count] = localCache.getSliceFor(value); auto t = typename T::self_t({table->asArrowTable()->Slice(static_cast(offset), count)}, static_cast(offset)); - table->copyIndexBindings(t); + if (t.tableSize() != 0) { + table->copyIndexBindings(t); + } return t; } -template +template auto doFilteredSliceByCached(T const* table, framework::expressions::BindingNode const& node, int value, o2::framework::SliceCache& cache) { - auto localCache = cache.ptr->getCacheFor({o2::soa::getLabelFromTypeForKey(node.name), node.name}); + auto localCache = cache.ptr->getCacheFor({"", o2::soa::getMatcherFromTypeForKey(node.name), node.name}); auto [offset, count] = localCache.getSliceFor(value); auto slice = table->asArrowTable()->Slice(static_cast(offset), count); return prepareFilteredSlice(table, slice, offset); } -template +template auto doSliceByCachedUnsorted(T const* table, framework::expressions::BindingNode const& node, int value, o2::framework::SliceCache& cache) { - auto localCache = cache.ptr->getCacheUnsortedFor({o2::soa::getLabelFromTypeForKey(node.name), node.name}); + auto localCache = cache.ptr->getCacheUnsortedFor({"", o2::soa::getMatcherFromTypeForKey(node.name), node.name}); if constexpr (soa::is_filtered_table) { auto t = typename T::self_t({table->asArrowTable()}, localCache.getSliceFor(value)); - t.intersectWithSelection(table->getSelectedRows()); - table->copyIndexBindings(t); + if (t.tableSize() != 0) { + t.intersectWithSelection(table->getSelectedRows()); + table->copyIndexBindings(t); + } return t; } else { auto t = Filtered({table->asArrowTable()}, localCache.getSliceFor(value)); - table->copyIndexBindings(t); + if (t.tableSize() != 0) { + table->copyIndexBindings(t); + } return t; } } @@ -1703,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 @@ -1720,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) @@ -1733,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> @@ -1749,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()); @@ -1949,8 +2087,8 @@ class Table Table(std::shared_ptr table, uint64_t offset = 0) : mTable(table), - mEnd{table->num_rows()}, - mOffset(offset) + mOffset(offset), + mEnd{table->num_rows()} { if (mTable->num_rows() == 0) { for (size_t ci = 0; ci < framework::pack_size(columns_t{}); ++ci) { @@ -2076,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 @@ -2261,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"); @@ -2324,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_) \ @@ -2624,7 +2777,7 @@ consteval auto getIndexTargets() return !(*mColumnIterator).empty(); \ } \ \ - template \ + template \ auto _Getter_##_as() const \ { \ if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ @@ -2634,10 +2787,15 @@ consteval auto getIndexTargets() if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ } \ - return getIterators(); \ + auto result = std::vector(); \ + result.reserve((*mColumnIterator).size()); \ + for (auto& i : *mColumnIterator) { \ + result.emplace_back(t->rawIteratorAt(i)); \ + } \ + return result; \ } \ \ - template \ + template \ auto filtered_##_Getter_##_as() const \ { \ if (O2_BUILTIN_UNLIKELY(mBinding.ptr == nullptr)) { \ @@ -2647,35 +2805,15 @@ consteval auto getIndexTargets() if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ o2::soa::dereferenceWithWrongType(#_Getter_, #_Table_); \ } \ - return getFilteredIterators(); \ - } \ - \ - template \ - auto getIterators() const \ - { \ - auto result = std::vector(); \ - for (auto& i : *mColumnIterator) { \ - result.push_back(mBinding.get()->rawIteratorAt(i)); \ - } \ - return result; \ - } \ - \ - template \ - std::vector getFilteredIterators() const \ - { \ - if constexpr (o2::soa::is_filtered_table) { \ - auto result = std::vector(); \ - for (auto const& i : *mColumnIterator) { \ - auto pos = mBinding.get()->isInSelectedRows(i); \ - if (pos > 0) { \ - result.emplace_back(mBinding.get()->iteratorAt(pos)); \ - } \ + auto result = std::vector(); \ + result.reserve((*mColumnIterator).size()); \ + for (auto const& i : *mColumnIterator) { \ + auto pos = t->isInSelectedRows(i); \ + if (pos > 0) { \ + result.emplace_back(t->iteratorAt(pos)); \ } \ - return result; \ - } else { \ - static_assert(o2::framework::always_static_assert_v, "T is not a Filtered type"); \ } \ - return {}; \ + return result; \ } \ \ auto _Getter_() const \ @@ -2820,7 +2958,7 @@ consteval auto getIndexTargets() o2::soa::Binding getCurrentRaw() const { return mBinding; } \ o2::soa::Binding mBinding; \ }; \ - [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" #_Table_ _Suffix_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } + [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_##Id { "fIndex" _Label_ _Suffix_, _Name_##Id::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_INDEX_COLUMN_FULL(_Name_, _Getter_, _Type_, _Table_, _Suffix_) DECLARE_SOA_INDEX_COLUMN_FULL_CUSTOM(_Name_, _Getter_, _Type_, _Table_, #_Table_, _Suffix_) #define DECLARE_SOA_INDEX_COLUMN(_Name_, _Getter_) DECLARE_SOA_INDEX_COLUMN_FULL(_Name_, _Getter_, int32_t, _Name_##s, "") @@ -2990,15 +3128,9 @@ consteval auto getIndexTargets() if (O2_BUILTIN_UNLIKELY(t == nullptr)) { \ o2::soa::dereferenceWithWrongType(#_Getter_, "self"); \ } \ - return getIterators(); \ - } \ - \ - template \ - auto getIterators() const \ - { \ auto result = std::vector(); \ for (auto& i : *mColumnIterator) { \ - result.push_back(mBinding.get()->rawIteratorAt(i)); \ + result.push_back(t->rawIteratorAt(i)); \ } \ return result; \ } \ @@ -3162,154 +3294,199 @@ 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__>{}); \ - }; \ - 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} { - bindInternalIndicesTo(this); + 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} { - bindInternalIndicesTo(this); + if (this->tableSize() != 0) { + bindInternalIndicesTo(this); + } } 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_; - using self_t = JoinFull; + template + void bindExternalIndices(TA*... current) + { + ([this](TA* cur) { + if constexpr (binding_origin == TA::binding_origin) { + this->bindExternalIndex(cur); + } + }(current), + ...); + } + + using self_t = Join; using table_t = base; static constexpr const auto originals = base::originals; static constexpr const auto originalLabels = base::originalLabels; @@ -3373,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) { @@ -3387,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; @@ -3437,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; @@ -3558,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)); @@ -4047,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 2a9e1b61ee6df..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 { @@ -404,6 +406,16 @@ DECLARE_SOA_DYNAMIC_COLUMN(HasTOF, hasTOF, //! Flag to check if track has a TOF [](uint8_t detectorMap) -> bool { return detectorMap & o2::aod::track::TOF; }); DECLARE_SOA_DYNAMIC_COLUMN(IsPVContributor, isPVContributor, //! Run 3: Has this track contributed to the collision vertex fit [](uint8_t flags) -> bool { return (flags & o2::aod::track::PVContributor) == o2::aod::track::PVContributor; }); +DECLARE_SOA_DYNAMIC_COLUMN(HasTPCSideA, hasTPCSideA, //! Run 3: Has this track TPC clusters from side A? + [](uint8_t flags) -> bool { return (flags & o2::aod::track::TPCSideA) == o2::aod::track::TPCSideA; }); +DECLARE_SOA_DYNAMIC_COLUMN(HasTPCSideAOnly, hasTPCSideAOnly, //! Run 3: Has this track TPC clusters from side A only? + [](uint8_t flags) -> bool { return (flags & (o2::aod::track::TPCSideA | o2::aod::track::TPCSideC)) == o2::aod::track::TPCSideA; }); +DECLARE_SOA_DYNAMIC_COLUMN(HasTPCSideC, hasTPCSideC, //! Run 3: Has this track TPC clusters from side C? + [](uint8_t flags) -> bool { return (flags & o2::aod::track::TPCSideC) == o2::aod::track::TPCSideC; }); +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); }); 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 @@ -460,13 +472,13 @@ DECLARE_SOA_DYNAMIC_COLUMN(TPCFractionSharedCls, tpcFractionSharedCls, //! Fract return (float)tpcNClsShared / (float)tpcNClsFound; }); -DECLARE_SOA_DYNAMIC_COLUMN(TRDHasNeighbor, trdPattern, //! Flag to check if at least one tracklet of a TRD Track has a neighboring tracklet +DECLARE_SOA_DYNAMIC_COLUMN(TRDHasNeighbor, trdHasNeighbor, //! Flag to check if at least one tracklet of a TRD Track has a neighboring tracklet [](uint8_t trdPattern) -> bool { return trdPattern & o2::aod::track::HasNeighbor; }); -DECLARE_SOA_DYNAMIC_COLUMN(TRDHasCrossing, trdPattern, //! Flag to check if at least one tracklet of a TRD Track crossed a padrow +DECLARE_SOA_DYNAMIC_COLUMN(TRDHasCrossing, trdHasCrossing, //! Flag to check if at least one tracklet of a TRD Track crossed a padrow [](uint8_t trdPattern) -> bool { return trdPattern & o2::aod::track::HasCrossing; }); -DECLARE_SOA_DYNAMIC_COLUMN(TRDNLayers, trdPattern, //! Number of TRD tracklets in a Track +DECLARE_SOA_DYNAMIC_COLUMN(TRDNTracklets, trdNTracklets, //! Number of TRD tracklets in a Track [](uint8_t trdPattern) -> std::size_t { return std::bitset<6>(trdPattern).count(); }); } // namespace track @@ -504,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, @@ -579,6 +591,7 @@ DECLARE_SOA_TABLE_FULL(StoredTracksExtra_000, "TracksExtra", "AOD", "TRACKEXTRA" track::TPCCrossedRowsOverFindableCls, track::TPCFoundOverFindableCls, track::TPCFractionSharedCls, + track::TRDHasCrossing, track::TRDHasNeighbor, track::TRDNTracklets, track::TrackEtaEMCAL, track::TrackPhiEMCAL, track::TrackTime, track::TrackTimeRes); DECLARE_SOA_TABLE_FULL_VERSIONED(StoredTracksExtra_001, "TracksExtra", "AOD", "TRACKEXTRA", 1, // On disk version of TracksExtra, version 1 @@ -608,6 +621,7 @@ DECLARE_SOA_TABLE_FULL_VERSIONED(StoredTracksExtra_001, "TracksExtra", "AOD", "T track::TPCCrossedRowsOverFindableCls, track::TPCFoundOverFindableCls, track::TPCFractionSharedCls, + track::TRDHasCrossing, track::TRDHasNeighbor, track::TRDNTracklets, track::TrackEtaEMCAL, track::TrackPhiEMCAL, track::TrackTime, track::TrackTimeRes); DECLARE_SOA_TABLE_FULL_VERSIONED(StoredTracksExtra_002, "TracksExtra", "AOD", "TRACKEXTRA", 2, // On disk version of TracksExtra, version 2 @@ -638,6 +652,7 @@ DECLARE_SOA_TABLE_FULL_VERSIONED(StoredTracksExtra_002, "TracksExtra", "AOD", "T track::TPCCrossedRowsOverFindableCls, track::TPCFoundOverFindableCls, track::TPCFractionSharedCls, + track::TRDHasCrossing, track::TRDHasNeighbor, track::TRDNTracklets, track::TrackEtaEMCAL, track::TrackPhiEMCAL, track::TrackTime, track::TrackTimeRes); DECLARE_SOA_EXTENDED_TABLE(TracksExtra_000, StoredTracksExtra_000, "EXTRACKEXTRA", 0, //! Additional track information (clusters, PID, etc.) @@ -667,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 { @@ -913,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. { @@ -992,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 { @@ -1600,6 +1617,26 @@ DECLARE_SOA_TABLE(FDDsExtra, "AOD", "FDDEXTRA", //! FDDsExtra table fdd::TimeFDDA, fdd::TimeFDDC); using FDDExtra = FDDsExtra::iterator; +namespace trd +{ +DECLARE_SOA_INDEX_COLUMN(Track, track); //! Track index +DECLARE_SOA_COLUMN(TRDQ0s, trdQ0s, int[6]); //! Q0 charge (un-corrected) +DECLARE_SOA_COLUMN(TRDQ1s, trdQ1s, int[6]); //! Q1 charge (un-corrected) +DECLARE_SOA_COLUMN(TRDQ2s, trdQ2s, int[6]); //! Q2 charge (un-corrected) +DECLARE_SOA_COLUMN(TRDQ0sCorrected, trdQ0sCorrected, float[6]); //! Q0 charge (corrected) +DECLARE_SOA_COLUMN(TRDQ1sCorrected, trdQ1sCorrected, float[6]); //! Q1 charge (corrected) +DECLARE_SOA_COLUMN(TRDQ2sCorrected, trdQ2sCorrected, float[6]); //! Q2 charge (corrected) +DECLARE_SOA_COLUMN(TRDTgls, trdTgls, float[6]); //! Local tracklet TgL +DECLARE_SOA_COLUMN(TRDPhis, trdPhis, float[6]); //! Local tracklet phi +} // namespace trd + +DECLARE_SOA_TABLE(TRDsExtra, "AOD", "TRDEXTRA", //! TRDExtra table + o2::soa::Index<>, trd::TrackId, + trd::TRDQ0s, trd::TRDQ1s, trd::TRDQ2s, + trd::TRDQ0sCorrected, trd::TRDQ1sCorrected, trd::TRDQ2sCorrected, + trd::TRDTgls, trd::TRDPhis); +using TRDExtra = TRDsExtra::iterator; + namespace v0 { DECLARE_SOA_INDEX_COLUMN_FULL(PosTrack, posTrack, int, Tracks, "_Pos"); //! Positive track @@ -1993,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 { @@ -2017,7 +2056,7 @@ namespace aod namespace mctracklabel { DECLARE_SOA_INDEX_COLUMN(McParticle, mcParticle); //! MC particle -DECLARE_SOA_COLUMN(McMask, mcMask, uint16_t); //! Bit mask to indicate detector mismatches (bit ON means mismatch). Bit 0-6: mismatch at ITS layer. Bit 7-9: # of TPC mismatches in the ranges 0, 1, 2-3, 4-7, 8-15, 16-31, 32-63, >64. Bit 10: TRD, bit 11: TOF, bit 15: indicates negative label +DECLARE_SOA_COLUMN(McMask, mcMask, uint16_t); //! Bit mask to indicate detector mismatches (bit ON means mismatch). Bit 0-6: mismatch at ITS layer. Bit 12: ITSAB tracklet mismatch. Bit 13: ITS-TPC mismatch. Bit 14: isNoise == True (global track), Bit 15: isFake == True (global track) } // namespace mctracklabel DECLARE_SOA_TABLE(McTrackLabels, "AOD", "MCTRACKLABEL", //! Table joined to the track table containing the MC index @@ -2158,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", //! @@ -2192,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 842263cd75abc..cfd2f357ba06f 100644 --- a/Framework/Core/include/Framework/AnalysisHelpers.h +++ b/Framework/Core/include/Framework/AnalysisHelpers.h @@ -26,6 +26,154 @@ #include "Framework/Traits.h" #include +namespace o2::soa +{ +struct IndexRecord { + std::string label; + framework::ConcreteDataMatcher matcher; + std::string columnLabel; + IndexKind kind; + int pos; + std::shared_ptr type = [](IndexKind kind) -> std::shared_ptr { + switch (kind) { + case IndexKind::IdxSingle: + case IndexKind::IdxSelf: + return arrow::int32(); + case IndexKind::IdxSlice: + return arrow::fixed_size_list(arrow::int32(), 2); + case IndexKind::IdxArray: + return arrow::list(arrow::int32()); + default: + return {nullptr}; + } + }(kind); + + auto operator==(IndexRecord const& other) const + { + return (this->label == other.label) && (this->columnLabel == other.columnLabel) && (this->kind == other.kind) && (this->pos == other.pos); + } + + std::shared_ptr field() const + { + return std::make_shared(columnLabel, type); + } +}; + +struct IndexBuilder { + static std::vector makeBuilders(std::vector>&& tables, std::vector const& records); + static void resetBuilders(std::vector& builders, std::vector>&& tables); + + static std::shared_ptr materialize(std::vector& builders, std::vector>&& tables, std::vector const& records, std::shared_ptr const& schema, bool exclusive); +}; +} // namespace o2::soa + +namespace o2::framework +{ +std::shared_ptr makeEmptyTableImpl(const char* name, std::shared_ptr& schema); + +template +auto makeEmptyTable(const char* name) +{ + auto schema = std::make_shared(soa::createFieldsFromColumns(typename T::table_t::persistent_columns_t{})); + return makeEmptyTableImpl(name, schema); +} + +template + requires(soa::not_void>::metadata>) +auto makeEmptyTable() +{ + auto schema = std::make_shared(soa::createFieldsFromColumns(typename aod::MetadataTrait>::metadata::persistent_columns_t{})); + return makeEmptyTableImpl(o2::aod::label(), schema); +} + +template +auto makeEmptyTable(const char* name, framework::pack p) +{ + auto schema = std::make_shared(soa::createFieldsFromColumns(p)); + return makeEmptyTableImpl(name, schema); +} + +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{})); + return makeEmptyTableImpl(name, schema); +} + +std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, size_t nColumns, + expressions::Projector* projectors, const char* name, std::shared_ptr& projector); + +std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, + const char* name, size_t nColumns, + const std::shared_ptr& projector); + +/// Expression-based column generator to materialize columns +template + requires(soa::has_extension::metadata>) +auto spawner(std::shared_ptr const& fullTable, const char* name, o2::framework::expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) +{ + if (fullTable->num_rows() == 0) { + return makeEmptyTable(name); + } + constexpr auto Ncol = []() { + if constexpr (soa::has_configurable_extension) { + return framework::pack_size(typename M::placeholders_pack_t{}); + } else { + return framework::pack_size(typename M::expression_pack_t{}); + } + }.template operator()::metadata>(); + return spawnerHelper(fullTable, schema, Ncol, projectors, name, projector); +} + +template +auto spawner(framework::pack, std::vector>&& tables, const char* name, expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) +{ + std::array labels{"original"}; + auto fullTable = soa::ArrowHelpers::joinTables(std::move(tables), std::span{labels}); + if (fullTable->num_rows() == 0) { + return makeEmptyTable(name, framework::pack{}); + } + return spawnerHelper(fullTable, schema, sizeof...(C), projectors, name, projector); +} + +std::string serializeProjectors(std::vector& projectors); +std::string serializeSchema(std::shared_ptr schema); +std::string serializeIndexRecords(std::vector& irs); +std::vector> extractSources(ProcessingContext& pc, std::vector const& labels); + +struct Spawner { + std::string binding; + std::vector labels; + std::vector matchers; + std::vector> expressions; + std::shared_ptr projector = nullptr; + std::shared_ptr schema = nullptr; + std::shared_ptr inputSchema = nullptr; + + header::DataOrigin origin; + header::DataDescription description; + header::DataHeader::SubSpecificationType version; + + std::shared_ptr materialize(ProcessingContext& pc) const; +}; + +struct Builder { + bool exclusive; + std::vector labels; + std::vector matchers; + std::vector records; + std::shared_ptr outputSchema; + header::DataOrigin origin; + header::DataDescription description; + header::DataHeader::SubSpecificationType version; + + std::shared_ptr> builders = nullptr; + + std::shared_ptr materialize(ProcessingContext& pc); +}; +} // namespace o2::framework + namespace o2::soa { template @@ -38,6 +186,16 @@ constexpr auto tableRef2ConfigParamSpec() {"\"\""}}; } +template +constexpr auto tableRef2Schema() +{ + return o2::framework::ConfigParamSpec{ + std::string{"input-schema:"} + o2::aod::label(), + framework::VariantType::String, + framework::serializeSchema(o2::aod::MetadataTrait>::metadata::getSchema()), + {"\"\""}}; +} + namespace { template @@ -50,6 +208,36 @@ inline constexpr auto getSources() }.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 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() { @@ -63,20 +251,91 @@ inline constexpr auto getCCDBUrls() return result; } +template + requires(std::same_as) +consteval IndexKind getIndexKind() +{ + return IndexKind::IdxSingle; +} + +template + requires(std::is_bounded_array_v) +consteval IndexKind getIndexKind() +{ + return IndexKind::IdxSlice; +} + +template + requires(framework::is_specialization_v) +consteval IndexKind getIndexKind() +{ + return IndexKind::IdxArray; +} + +template +inline constexpr auto getIndexMapping() +{ + std::vector idx; + using indices = T::index_pack_t; + using Key = T::Key; + [&idx](std::index_sequence) mutable { + constexpr auto refs = T::generateSources(); + ([&idx]() mutable { + constexpr auto pos = o2::aod::MetadataTrait>::metadata::template getIndexPosToKey(); + if constexpr (pos == -1) { + idx.emplace_back(o2::aod::label(), o2::aod::matcher(), C::columnLabel(), IndexKind::IdxSelf, pos); + } else { + idx.emplace_back(o2::aod::label(), o2::aod::matcher(), C::columnLabel(), getIndexKind(), pos); + } + }.template operator()>(), + ...); + }(std::make_index_sequence()); + ; + 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 { 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 - requires(!soa::with_sources) + requires(!(soa::with_sources || soa::with_sources_generator)) constexpr auto getInputMetadata() -> std::vector { return {}; @@ -97,16 +356,66 @@ constexpr auto getCCDBMetadata() -> std::vector { return {}; } -} // namespace + +template +constexpr auto getExpressionMetadata() -> std::vector +{ + using expression_pack_t = T::expression_pack_t; + + auto projectors = [](framework::pack) -> std::vector { + std::vector result; + (result.emplace_back(std::move(C::Projector())), ...); + return result; + }(expression_pack_t{}); + + auto json = framework::serializeProjectors(projectors); + return {framework::ConfigParamSpec{"projectors", framework::VariantType::String, json, {"\"\""}}}; +} + +template + requires(!soa::with_expression_pack) +constexpr auto getExpressionMetadata() -> std::vector +{ + return {}; +} + +template +constexpr auto getIndexMetadata() -> std::vector +{ + auto map = getIndexMapping(); + return {framework::ConfigParamSpec{"index-records", framework::VariantType::String, framework::serializeIndexRecords(map), {"\"\""}}, + {framework::ConfigParamSpec{"index-exclusive", framework::VariantType::Bool, T::exclusive, {"\"\""}}}}; +} + +template + requires(!soa::with_index_pack) +constexpr auto getIndexMetadata() -> std::vector +{ + return {}; +} + +} // 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()); + 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()), {"\"\""}}); + } return framework::InputSpec{ o2::aod::label(), @@ -120,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 @@ -143,7 +463,7 @@ constexpr auto tableRef2OutputRef() o2::aod::label(), R.version}; } -} // namespace o2::soa +} // namespace o2::soa namespace o2::framework { @@ -242,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(); } }; @@ -282,32 +602,32 @@ 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 constexpr auto base_spec() + static auto base_spec() { return soa::tableRef2InputSpec(); } static auto base_specs() { - return [](std::index_sequence) -> std::vector { - return {base_spec()...}; + return [](std::index_sequence) { + return std::array{base_spec()...}; }(std::make_index_sequence{}); } - constexpr auto spec() const + static constexpr auto spec() { return soa::tableRef2OutputSpec(); } - constexpr auto output() const + static constexpr auto output() { return soa::tableRef2Output(); } - constexpr auto ref() const + static constexpr auto ref() { return soa::tableRef2OutputRef(); } @@ -316,32 +636,26 @@ 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 base_table_t = typename metadata::base_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{}); - constexpr auto pack() - { - return expression_pack_t{}; - } - typename T::table_t* operator->() { return table.get(); @@ -355,21 +669,26 @@ struct Spawns : decltype(transformBase()) { { return extension->asArrowTable(); } + 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())...}}; } (expression_pack_t{}); std::shared_ptr projector = nullptr; - std::shared_ptr schema = std::make_shared(o2::soa::createFieldsFromColumns(expression_pack_t{})); + std::shared_ptr schema = []() { + auto s = std::make_shared(o2::soa::createFieldsFromColumns(expression_pack_t{})); + s->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{o2::aod::label()}})); + return s; + }(); }; template concept is_spawns = requires(T t) { typename T::metadata; - requires std::same_as; + typename T::expression_pack_t; requires std::same_as>; }; @@ -383,16 +702,10 @@ 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 base_table_t = typename metadata::base_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{}); - constexpr auto pack() - { - return placeholders_pack_t{}; - } - typename T::table_t* operator->() { return table.get(); @@ -411,7 +724,11 @@ struct Defines : decltype(transformBase()) { std::array projectors; std::shared_ptr projector = nullptr; - std::shared_ptr schema = std::make_shared(o2::soa::createFieldsFromColumns(placeholders_pack_t{})); + std::shared_ptr schema = []() { + auto s = std::make_shared(o2::soa::createFieldsFromColumns(placeholders_pack_t{})); + s->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{o2::aod::label()}})); + return s; + }(); std::shared_ptr inputSchema = nullptr; bool needRecompilation = false; @@ -428,7 +745,7 @@ using DefinesDelayed = Defines; template concept is_defines = requires(T t) { typename T::metadata; - requires std::same_as; + typename T::placeholders_pack_t; requires std::same_as>; requires std::same_as; &T::recompile; @@ -443,129 +760,6 @@ struct Exclusive { struct Sparse { }; -namespace -{ -template -inline std::shared_ptr getIndexToKey(arrow::Table* table) -{ - using IC = framework::pack_element_t(typename T::external_index_columns_t{}), typename T::external_index_columns_t>; - return table->column(framework::has_type_at_v(typename T::persistent_columns_t{})); -} - -template -struct ColumnTrait { - using column_t = C; - - static consteval auto listSize() - { - if constexpr (std::same_as>) { - return -1; - } else if constexpr (std::same_as) { - return 2; - } else { - return 1; - } - } - - template - static std::shared_ptr makeColumnBuilder(arrow::Table* table, arrow::MemoryPool* pool) - { - if constexpr (!std::same_as) { - return std::make_shared(getIndexToKey(table), C::columnLabel(), listSize(), pool); - } else { - return std::make_shared(C::columnLabel(), pool); - } - } -}; - -template -struct Reduction { - using type = typename std::conditional(), SelfIndexColumnBuilder, IndexColumnBuilder>::type; -}; - -template -using reduced_t = Reduction::type; -} // namespace - -template -struct IndexBuilder { - template refs, typename C1, typename... Cs> - static auto indexBuilder(const char* label, std::vector>&& tables, framework::pack) - { - auto pool = arrow::default_memory_pool(); - SelfIndexColumnBuilder self{C1::columnLabel(), pool}; - std::unique_ptr keyIndex = nullptr; - if constexpr (!Key::template hasOriginal()) { - keyIndex = std::make_unique(tables[0]->column(o2::aod::MetadataTrait>::metadata::template getIndexPosToKey())); - } - - auto sq = std::make_index_sequence(); - - auto columnBuilders = [&tables, &pool ](std::index_sequence) -> std::array, sizeof...(Cs)> - { - return {[](arrow::Table* table, arrow::MemoryPool* pool) { - using T = framework::pack_element_t>; - if constexpr (!Key::template hasOriginal()) { - constexpr auto pos = o2::aod::MetadataTrait>::metadata::template getIndexPosToKey(); - return std::make_shared(table->column(pos), T::columnLabel(), ColumnTrait::listSize(), pool); - } else { - return std::make_shared(T::columnLabel(), pool); - } - }(tables[Is + 1].get(), pool)...}; - } - (sq); - - std::array finds; - - for (int64_t counter = 0; counter < tables[0]->num_rows(); ++counter) { - int64_t idx = -1; - if constexpr (Key::template hasOriginal()) { - idx = counter; - } else { - idx = keyIndex->valueAt(counter); - } - finds = [&idx, &columnBuilders](std::index_sequence) { - return std::array{ - [&idx, &columnBuilders]() { - using T = typename framework::pack_element_t>; - return std::static_pointer_cast>(columnBuilders[Is])->template find(idx); - }()...}; - }(sq); - if constexpr (std::same_as) { - [&idx, &columnBuilders](std::index_sequence) { - ([&idx, &columnBuilders]() { - using T = typename framework::pack_element_t>; - return std::static_pointer_cast>(columnBuilders[Is])->template fill(idx); }(), ...); - }(sq); - self.fill(counter); - } else if constexpr (std::same_as) { - if (std::none_of(finds.begin(), finds.end(), [](bool const x) { return x == false; })) { - [&idx, &columnBuilders](std::index_sequence) { - ([&idx, &columnBuilders]() { - using T = typename framework::pack_element_t>; - return std::static_pointer_cast>(columnBuilders[Is])->template fill(idx); - }(), - ...); - }(sq); - self.fill(counter); - } - } - } - - return [&label, &columnBuilders, &self](std::index_sequence) { - return makeArrowTable(label, - {self.template result(), [&columnBuilders]() { - using T = typename framework::pack_element_t>; - return std::static_pointer_cast>(columnBuilders[Is])->template result(); - }()...}, - {self.field(), [&columnBuilders]() { - using T = typename framework::pack_element_t>; - return std::static_pointer_cast>(columnBuilders[Is])->field(); - }()...}); - }(sq); - } -}; - /// This helper struct allows you to declare index tables to be created in a task template @@ -579,12 +773,17 @@ template struct Builds : decltype(transformBase()) { using buildable_t = T; using metadata = decltype(transformBase())::metadata; - using IP = std::conditional_t, IndexBuilder>; using Key = metadata::Key; using H = typename T::first_t; using Ts = typename T::rest_t; using index_pack_t = metadata::index_pack_t; + std::shared_ptr outputSchema = []() { return std::make_shared(soa::createFieldsFromColumns(index_pack_t{}))->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{o2::aod::label()}})); }(); + + std::vector map = soa::getIndexMapping(); + + std::vector builders; + T* operator->() { return table.get(); @@ -605,10 +804,9 @@ struct Builds : decltype(transformBase()) { return index_pack_t{}; } - template - auto build(framework::pack, std::vector>&& tables) + auto build(std::vector>&& tables) { - this->table = std::make_shared(IP::template indexBuilder(o2::aod::label(), std::forward>>(tables), framework::pack{})); + this->table = std::make_shared(soa::IndexBuilder::materialize(builders, std::forward>>(tables), map, outputSchema, metadata::exclusive)); return (this->table != nullptr); } }; @@ -617,7 +815,7 @@ template concept is_builds = requires(T t) { typename T::metadata; typename T::Key; - requires std::same_as; + requires std::same_as>; }; /// This helper class allows you to declare things which will be created by a @@ -879,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 { @@ -902,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 6c43bf3eebebb..1873f33937742 100644 --- a/Framework/Core/include/Framework/AnalysisManagers.h +++ b/Framework/Core/include/Framework/AnalysisManagers.h @@ -11,6 +11,7 @@ #ifndef FRAMEWORK_ANALYSISMANAGERS_H #define FRAMEWORK_ANALYSISMANAGERS_H +#include "DataAllocator.h" #include "Framework/AnalysisHelpers.h" #include "Framework/DataSpecUtils.h" #include "Framework/GroupedCombinations.h" @@ -33,23 +34,11 @@ namespace o2::framework namespace { -template -static inline auto extractOriginal(ProcessingContext& pc) -{ - return pc.inputs().get(aod::MetadataTrait::metadata::tableLabel())->asArrowTable(); -} - -template -static inline std::vector> extractOriginals(framework::pack, ProcessingContext& pc) -{ - return {extractOriginal(pc)...}; -} - template refs> static inline auto extractOriginals(ProcessingContext& pc) { return [&](std::index_sequence) -> std::vector> { - return {pc.inputs().get(o2::aod::label())->asArrowTable()...}; + return {pc.inputs().get(o2::aod::matcher())->asArrowTable()...}; }(std::make_index_sequence()); } } // namespace @@ -159,12 +148,12 @@ const char* controlOption() } template -concept with_base_table = requires(T const& t) { t.base_specs(); }; +concept with_base_table = requires { T::base_specs(); }; template -bool requestInputs(std::vector& inputs, T const& entity) +bool requestInputs(std::vector& inputs, T const& /*entity*/) { - auto base_specs = entity.base_specs(); + auto base_specs = T::base_specs(); for (auto base_spec : base_specs) { base_spec.metadata.push_back(ConfigParamSpec{std::string{controlOption()}, VariantType::Bool, true, {"\"\""}}); DataSpecUtils::updateInputList(inputs, std::forward(base_spec)); @@ -181,7 +170,7 @@ bool newDataframeCondition(InputRecord&, C&) template bool newDataframeCondition(InputRecord& record, C& condition) { - condition.instance = (typename C::type*)record.get(condition.path).get(); + condition.instance = (typename C::type*)record.get(condition.path).release(); return true; } @@ -194,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; @@ -247,7 +236,10 @@ template bool postRunOutput(EndOfStreamContext& context, T& hr) { auto& deviceSpec = context.services().get(); - context.outputs().snapshot(hr.ref(deviceSpec.inputTimesliceId, deviceSpec.maxInputTimeslices), *(hr.getListOfHistograms())); + auto sendHistos = [deviceSpec, &context](HistogramRegistry const& self, TNamed* obj) mutable { + context.outputs().snapshot(self.ref(deviceSpec.inputTimesliceId, deviceSpec.maxInputTimeslices), *obj); + }; + hr.apply(sendHistos); hr.clean(); return true; } @@ -269,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; } @@ -283,11 +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}); - if (originalTable->schema()->fields().empty() == true) { - using base_table_t = typename T::base_table_t::table_t; - originalTable = makeEmptyTable(o2::aod::label()); + 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("EMPTY", typename metadata::base_table_t::persistent_columns_t{}); } using D = o2::aod::Hash; @@ -304,18 +295,17 @@ template bool prepareOutput(ProcessingContext& context, T& builds) { using metadata = o2::aod::MetadataTrait>::metadata; - return builds.template build(builds.pack(), 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}); - if (originalTable->schema()->fields().empty() == true) { - using base_table_t = typename T::base_table_t::table_t; - originalTable = makeEmptyTable(o2::aod::label()); + 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("EMPTY", typename metadata::base_table_t::persistent_columns_t{}); } if (defines.inputSchema == nullptr) { defines.inputSchema = originalTable->schema(); @@ -346,9 +336,8 @@ bool prepareDelayedOutput(ProcessingContext& context, T& defines) } using metadata = o2::aod::MetadataTrait>::metadata; auto originalTable = soa::ArrowHelpers::joinTables(extractOriginals(context), std::span{metadata::base_table_t::originalLabels}); - if (originalTable->schema()->fields().empty() == true) { - using base_table_t = typename T::base_table_t::table_t; - originalTable = makeEmptyTable(o2::aod::label()); + if (originalTable->num_rows() == 0) { + originalTable = makeEmptyTable(); } if (defines.inputSchema == nullptr) { defines.inputSchema = originalTable->schema(); @@ -545,12 +534,6 @@ void bindExternalIndicesPartition(P& partition, T*... tables) } /// Cache handling -template -bool preInitializeCache(InitContext&, T&) -{ - return false; -} - template bool initializeCache(ProcessingContext&, T&) { @@ -597,7 +580,7 @@ bool registerCache(T& preslice, Cache& bsks, Cache&) return true; } } - auto locate = std::find_if(bsks.begin(), bsks.end(), [&](auto const& entry) { return (entry.binding == preslice.bindingKey.binding) && (entry.key == preslice.bindingKey.key); }); + auto locate = std::find(bsks.begin(), bsks.end(), preslice.getBindingKey()); if (locate == bsks.end()) { bsks.emplace_back(preslice.getBindingKey()); } else if (locate->enabled == false) { @@ -615,7 +598,7 @@ bool registerCache(T& preslice, Cache&, Cache& bsksU) return true; } } - auto locate = std::find_if(bsksU.begin(), bsksU.end(), [&](auto const& entry) { return (entry.binding == preslice.bindingKey.binding) && (entry.key == preslice.bindingKey.key); }); + auto locate = std::find(bsksU.begin(), bsksU.end(), preslice.getBindingKey()); if (locate == bsksU.end()) { bsksU.emplace_back(preslice.getBindingKey()); } else if (locate->enabled == false) { diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index cc4d45a46c8bc..c1968123e765d 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -14,18 +14,17 @@ #include "Framework/OutputSpec.h" #include "Framework/InputSpec.h" #include "Framework/DataProcessorSpec.h" -#include "Framework/AnalysisContext.h" +#include "Framework/DanglingEdgesContext.h" #include "Headers/DataHeader.h" #include 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 { @@ -39,11 +38,6 @@ struct AnalysisSupportHelpers { std::vector const& requestedSpecials, std::vector& requestedAODs, DataProcessorSpec& publisher); - static void addMissingOutputsToAnalysisCCDBFetcher(std::vector const& providedSpecials, - std::vector const& requestedSpecials, - std::vector& requestedAODs, - std::vector& requestedDYNs, - DataProcessorSpec& publisher); static void addMissingOutputsToBuilder(std::vector const& requestedSpecials, std::vector& requestedAODs, std::vector& requestedDYNs, diff --git a/Framework/Core/include/Framework/AnalysisTask.h b/Framework/Core/include/Framework/AnalysisTask.h index 53f6bc0f862d6..1b25727874749 100644 --- a/Framework/Core/include/Framework/AnalysisTask.h +++ b/Framework/Core/include/Framework/AnalysisTask.h @@ -16,13 +16,13 @@ #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" #include "Framework/EndOfStreamContext.h" #include "Framework/GroupSlicer.h" #include "Framework/StructToTuple.h" -#include "Framework/Traits.h" #include "Framework/TypeIdHelpers.h" #include "Framework/ArrowTableSlicingCache.h" #include "Framework/AnalysisDataModel.h" @@ -37,6 +37,9 @@ namespace o2::framework { +/// Convert a CamelCase task struct name to snake-case task name +std::string type_to_task_name(std::string_view const& camelCase); + /// A more familiar task API for the DPL analysis framework. /// This allows you to define your own tasks as subclasses /// of o2::framework::AnalysisTask and to pass them in the specification @@ -63,28 +66,31 @@ static constexpr bool is_enumeration_v> = true; template concept is_enumeration = is_enumeration_v>; +template +concept is_table_iterator_or_enumeration = soa::is_table_or_iterator || is_enumeration; + // Helper struct which builds a DataProcessorSpec from // the contents of an AnalysisTask... namespace { struct AnalysisDataProcessorBuilder { - template + template static void addGroupingCandidates(Cache& bk, Cache& bku, bool enabled) { - [&bk, &bku, enabled](framework::pack) mutable { + [](framework::pack, Cache& bk, Cache& bku, bool enabled) { auto key = std::string{"fIndex"} + o2::framework::cutString(soa::getLabelFromType>()); - ([&bk, &bku, &key, enabled]() mutable { + ([](Cache& bk, Cache& bku, bool enabled, std::string const& key) { if constexpr (soa::relatedByIndex, std::decay_t>()) { - auto binding = soa::getLabelFromTypeForKey>(key); + Entry e{soa::getLabelFromTypeForKey>(key), soa::getMatcherFromTypeForKey>(key), key, enabled}; if constexpr (o2::soa::is_smallgroups>) { - framework::updatePairList(bku, binding, key, enabled); + framework::updatePairList(bku, e); } else { - framework::updatePairList(bk, binding, key, enabled); + framework::updatePairList(bk, e); } } - }(), + }(bk, bku, enabled, key), ...); - }(framework::pack{}); + }(framework::pack{}, bk, bku, enabled); } template @@ -168,8 +174,8 @@ struct AnalysisDataProcessorBuilder { return true; } /// 1. enumeration (must be the only argument) - template - static void inputsFromArgs(R (C::*)(A), const char* /*name*/, bool /*value*/, std::vector& inputs, std::vector&) //, Cache&, Cache&) + template + static void inputsFromArgs(void (C::*)(A), const char* /*name*/, bool /*value*/, std::vector& inputs, std::vector&) //, Cache&, Cache&) { std::vector inputMetadata; // FIXME: for the moment we do not support begin, end and step. @@ -177,44 +183,44 @@ struct AnalysisDataProcessorBuilder { } /// 2. 1st argument is an iterator - template - static void inputsFromArgs(R (C::*)(A, Args...), const char* name, bool value, std::vector& inputs, std::vector& eInfos) //, Cache& bk, Cache& bku) + template + static void inputsFromArgs(void (C::*)(A, Args...), const char* name, bool value, std::vector& inputs, std::vector& eInfos) //, Cache& bk, Cache& bku) requires(std::is_lvalue_reference_v && (std::is_lvalue_reference_v && ...)) { - constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); + constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); addInputsAndExpressions::parent_t, Args...>(hash, name, value, inputs, eInfos); } /// 3. generic case - template - static void inputsFromArgs(R (C::*)(Args...), const char* name, bool value, std::vector& inputs, std::vector& eInfos) //, Cache&, Cache&) + template + static void inputsFromArgs(void (C::*)(Args...), const char* name, bool value, std::vector& inputs, std::vector& eInfos) //, Cache&, Cache&) requires(std::is_lvalue_reference_v && ...) { - constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); + constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); addInputsAndExpressions(hash, name, value, inputs, eInfos); } /// 1. enumeration (no grouping) - template - static void cacheFromArgs(R (C::*)(A), bool, Cache&, Cache&) + template + static void cacheFromArgs(void (C::*)(A), bool, Cache&, Cache&) { } /// 2. iterator (the only grouping case) - template - static void cacheFromArgs(R (C::*)(A, Args...), bool value, Cache& bk, Cache& bku) + template + static void cacheFromArgs(void (C::*)(A, Args...), bool value, Cache& bk, Cache& bku) { addGroupingCandidates(bk, bku, value); } /// 3. generic case (no grouping) - template - static void cacheFromArgs(R (C::*)(A, Args...), bool, Cache&, Cache&) + template + static void cacheFromArgs(void (C::*)(A, Args...), bool, Cache&, Cache&) { } template static auto extractTableFromRecord(InputRecord& record) { - auto table = record.get(o2::aod::label())->asArrowTable(); + auto table = record.get(o2::aod::matcher())->asArrowTable(); if (table->num_rows() == 0) { table = makeEmptyTable(); } @@ -282,51 +288,53 @@ struct AnalysisDataProcessorBuilder { } } - template - static auto bindGroupingTable(InputRecord& record, R (C::*)(Grouping, Args...), std::vector& infos) + template + static auto bindGroupingTable(InputRecord& record, void (C::*)(Grouping, Args...), std::vector& infos) requires(!std::same_as) { - constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); + constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); return extract, 0>(record, infos, hash); } - template - static auto bindAssociatedTables(InputRecord& record, R (C::*)(Grouping, Args...), std::vector& infos) + template + static auto bindAssociatedTables(InputRecord& record, void (C::*)(Grouping, Args...), std::vector& infos) requires(!std::same_as && sizeof...(Args) > 0) { constexpr auto p = pack{}; - constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); + constexpr auto hash = o2::framework::TypeIdHelpers::uniqueId(); return std::make_tuple(extract, has_type_at_v(p) + 1>(record, infos, hash)...); } - template + template static void overwriteInternalIndices(std::tuple& dest, std::tuple const& src) { (std::get(dest).bindInternalIndicesTo(&std::get(src)), ...); } - template - static void invokeProcess(Task& task, InputRecord& inputs, R (C::*processingFunction)(Grouping, Associated...), std::vector& infos, ArrowTableSlicingCache& slices) + template + static void invokeProcess(Task& task, InputRecord& inputs, void (Task::*processingFunction)(Grouping, Associated...), std::vector& infos, ArrowTableSlicingCache& slices) { using G = std::decay_t; auto groupingTable = AnalysisDataProcessorBuilder::bindGroupingTable(inputs, processingFunction, infos); + constexpr const int numElements = nested_brace_constructible_size>() / 10; + // set filtered tables for partitions with grouping - homogeneous_apply_refs([&groupingTable](auto& element) { + homogeneous_apply_refs_sized([&groupingTable](auto& element) { analysis_task_parsers::setPartition(element, groupingTable); analysis_task_parsers::bindInternalIndicesPartition(element, &groupingTable); return true; }, - task); + task); if constexpr (sizeof...(Associated) == 0) { // single argument to process - homogeneous_apply_refs([&groupingTable](auto& element) { + homogeneous_apply_refs_sized([&groupingTable](auto& element) { analysis_task_parsers::bindExternalIndicesPartition(element, &groupingTable); analysis_task_parsers::setGroupedCombination(element, groupingTable); return true; }, - task); + task); if constexpr (soa::is_iterator) { for (auto& element : groupingTable) { std::invoke(processingFunction, task, *element); @@ -344,7 +352,7 @@ struct AnalysisDataProcessorBuilder { // pre-bind self indices std::apply( [&task](auto&... t) mutable { - (homogeneous_apply_refs( + (homogeneous_apply_refs_sized( [&t](auto& p) { analysis_task_parsers::bindInternalIndicesPartition(p, &t); return true; @@ -356,12 +364,12 @@ struct AnalysisDataProcessorBuilder { auto binder = [&task, &groupingTable, &associatedTables](auto& x) mutable { x.bindExternalIndices(&groupingTable, &std::get>(associatedTables)...); - homogeneous_apply_refs([&x](auto& t) mutable { + homogeneous_apply_refs_sized([&x](auto& t) mutable { analysis_task_parsers::setPartition(t, x); analysis_task_parsers::bindExternalIndicesPartition(t, &x); return true; }, - task); + task); }; groupingTable.bindExternalIndices(&std::get>(associatedTables)...); @@ -373,11 +381,11 @@ struct AnalysisDataProcessorBuilder { associatedTables); // GroupedCombinations bound separately, as they should be set once for all associated tables - homogeneous_apply_refs([&groupingTable, &associatedTables](auto& t) { + homogeneous_apply_refs_sized([&groupingTable, &associatedTables](auto& t) { analysis_task_parsers::setGroupedCombination(t, groupingTable, associatedTables); return true; }, - task); + task); overwriteInternalIndices(associatedTables, associatedTables); if constexpr (soa::is_iterator>) { auto slicer = GroupSlicer(groupingTable, associatedTables, slices); @@ -391,28 +399,28 @@ struct AnalysisDataProcessorBuilder { associatedSlices); // bind partitions and grouping table - homogeneous_apply_refs([&groupingTable](auto& x) { + homogeneous_apply_refs_sized([&groupingTable](auto& x) { analysis_task_parsers::bindExternalIndicesPartition(x, &groupingTable); return true; }, - task); + task); invokeProcessWithArgs(task, processingFunction, slice.groupingElement(), associatedSlices); } } else { // bind partitions and grouping table - homogeneous_apply_refs([&groupingTable](auto& x) { + homogeneous_apply_refs_sized([&groupingTable](auto& x) { analysis_task_parsers::bindExternalIndicesPartition(x, &groupingTable); return true; }, - task); + task); invokeProcessWithArgs(task, processingFunction, groupingTable, associatedTables); } } } - template + template static void invokeProcessWithArgs(C& task, T processingFunction, G g, std::tuple& at) { std::invoke(processingFunction, task, g, std::get(at)...); @@ -520,16 +528,18 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) std::vector options; std::vector expressionInfos; + constexpr const int numElements = nested_brace_constructible_size>() / 10; + /// make sure options and configurables are set before expression infos are created - homogeneous_apply_refs([&options, &hash](auto& element) { return analysis_task_parsers::appendOption(options, element); }, *task.get()); + homogeneous_apply_refs_sized([&options](auto& element) { return analysis_task_parsers::appendOption(options, element); }, *task.get()); /// extract conditions and append them as inputs - homogeneous_apply_refs([&inputs](auto& element) { return analysis_task_parsers::appendCondition(inputs, element); }, *task.get()); + homogeneous_apply_refs_sized([&inputs](auto& element) { return analysis_task_parsers::appendCondition(inputs, element); }, *task.get()); /// parse process functions defined by corresponding configurables if constexpr (requires { &T::process; }) { AnalysisDataProcessorBuilder::inputsFromArgs(&T::process, "default", true, inputs, expressionInfos); } - homogeneous_apply_refs( + homogeneous_apply_refs_sized( [name = name_str, &expressionInfos, &inputs](auto& x) mutable { // this pushes (argumentIndex, processHash, schemaPtr, nullptr) into expressionInfos for arguments that are Filtered/filtered_iterators return AnalysisDataProcessorBuilder::requestInputsFromArgs(x, name, inputs, expressionInfos); @@ -538,39 +548,50 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) // request base tables for spawnable extended tables and indices to be built // this checks for duplications - homogeneous_apply_refs([&inputs](auto& element) { + homogeneous_apply_refs_sized([&inputs](auto& element) { return analysis_task_parsers::requestInputs(inputs, element); }, - *task.get()); + *task.get()); // no static way to check if the task defines any processing, we can only make sure it subscribes to at least something if (inputs.empty() == true) { LOG(warn) << "Task " << name_str << " has no inputs"; } - homogeneous_apply_refs([&outputs, &hash](auto& element) { return analysis_task_parsers::appendOutput(outputs, element, hash); }, *task.get()); + // 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(); auto arrowServices = CommonServices::arrowServices(); requiredServices.insert(requiredServices.end(), arrowServices.begin(), arrowServices.end()); - homogeneous_apply_refs([&requiredServices](auto& element) { return analysis_task_parsers::addService(requiredServices, element); }, *task.get()); + homogeneous_apply_refs_sized([&requiredServices](auto& element) { return analysis_task_parsers::addService(requiredServices, element); }, *task.get()); auto algo = AlgorithmSpec::InitCallback{[task = task, expressionInfos](InitContext& ic) mutable { Cache bindingsKeys; Cache bindingsKeysUnsorted; // add preslice declarations to slicing cache definition - homogeneous_apply_refs([&bindingsKeys, &bindingsKeysUnsorted](auto& element) { return analysis_task_parsers::registerCache(element, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); + homogeneous_apply_refs_sized([&bindingsKeys, &bindingsKeysUnsorted](auto& element) { return analysis_task_parsers::registerCache(element, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); - homogeneous_apply_refs([&ic](auto&& element) { return analysis_task_parsers::prepareOption(ic, element); }, *task.get()); - homogeneous_apply_refs([&ic](auto&& element) { return analysis_task_parsers::prepareService(ic, element); }, *task.get()); + homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareOption(ic, element); }, *task.get()); + homogeneous_apply_refs_sized([&ic](auto&& element) { return analysis_task_parsers::prepareService(ic, element); }, *task.get()); auto& callbacks = ic.services().get(); auto eoscb = [task](EndOfStreamContext& eosContext) { - homogeneous_apply_refs([&eosContext](auto& element) { + homogeneous_apply_refs_sized([&eosContext](auto& element) { analysis_task_parsers::postRunService(eosContext, element); analysis_task_parsers::postRunOutput(eosContext, element); return true; }, - *task.get()); + *task.get()); eosContext.services().get().readyToQuit(QuitRequest::Me); }; @@ -582,84 +603,75 @@ DataProcessorSpec adaptAnalysisTask(ConfigContext const& ctx, Args&&... args) } /// update configurables in filters and partitions - homogeneous_apply_refs( + homogeneous_apply_refs_sized( [&ic](auto& element) -> bool { return analysis_task_parsers::updatePlaceholders(ic, element); }, *task.get()); /// create expression trees for filters gandiva trees matched to schemas and store the pointers into expressionInfos - homogeneous_apply_refs([&expressionInfos](auto& element) { + homogeneous_apply_refs_sized([&expressionInfos](auto& element) { return analysis_task_parsers::createExpressionTrees(expressionInfos, element); }, - *task.get()); + *task.get()); /// parse process functions to enable requested grouping caches - note that at this state process configurables have their final values if constexpr (requires { &T::process; }) { AnalysisDataProcessorBuilder::cacheFromArgs(&T::process, true, bindingsKeys, bindingsKeysUnsorted); } - homogeneous_apply_refs( - [&bindingsKeys, &bindingsKeysUnsorted](auto& x) mutable { + homogeneous_apply_refs_sized( + [&bindingsKeys, &bindingsKeysUnsorted](auto& x) { return AnalysisDataProcessorBuilder::requestCacheFromArgs(x, bindingsKeys, bindingsKeysUnsorted); }, *task.get()); ic.services().get().setCaches(std::move(bindingsKeys)); ic.services().get().setCachesUnsorted(std::move(bindingsKeysUnsorted)); - // initialize global caches - homogeneous_apply_refs([&ic](auto& element) { - return analysis_task_parsers::preInitializeCache(ic, element); - }, - *(task.get())); return [task, expressionInfos](ProcessingContext& pc) mutable { // load the ccdb object from their cache - homogeneous_apply_refs([&pc](auto& element) { return analysis_task_parsers::newDataframeCondition(pc.inputs(), element); }, *task.get()); + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::newDataframeCondition(pc.inputs(), element); }, *task.get()); // reset partitions once per dataframe - homogeneous_apply_refs([](auto& element) { return analysis_task_parsers::newDataframePartition(element); }, *task.get()); + homogeneous_apply_refs_sized([](auto& element) { return analysis_task_parsers::newDataframePartition(element); }, *task.get()); // reset selections for the next dataframe - for (auto& info : expressionInfos) { - info.resetSelection = true; - } + std::ranges::for_each(expressionInfos, [](auto& info) { info.resetSelection = true; }); // reset pre-slice for the next dataframe auto slices = pc.services().get(); - homogeneous_apply_refs([&pc, &slices](auto& element) { + homogeneous_apply_refs_sized([&slices](auto& element) { return analysis_task_parsers::updateSliceInfo(element, slices); }, - *(task.get())); + *(task.get())); // initialize local caches - homogeneous_apply_refs([&pc](auto& element) { return analysis_task_parsers::initializeCache(pc, element); }, *(task.get())); + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::initializeCache(pc, element); }, *(task.get())); // prepare outputs - homogeneous_apply_refs([&pc](auto& element) { return analysis_task_parsers::prepareOutput(pc, element); }, *task.get()); + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareOutput(pc, element); }, *task.get()); // execute run() if constexpr (requires { task->run(pc); }) { task->run(pc); } // execute process() - if constexpr (requires { AnalysisDataProcessorBuilder::invokeProcess(*(task.get()), pc.inputs(), &T::process, expressionInfos, slices); }) { + if constexpr (requires { &T::process; }) { AnalysisDataProcessorBuilder::invokeProcess(*(task.get()), pc.inputs(), &T::process, expressionInfos, slices); } // execute optional process() - homogeneous_apply_refs( - [&pc, &expressionInfos, &task, &slices](auto& x) mutable { - if constexpr (base_of_template>) { + homogeneous_apply_refs_sized( + [&pc, &expressionInfos, &task, &slices](auto& x) { + if constexpr (is_process_configurable) { if (x.value == true) { AnalysisDataProcessorBuilder::invokeProcess(*task.get(), pc.inputs(), x.process, expressionInfos, slices); return true; } + return false; } return false; }, *task.get()); // prepare delayed outputs - homogeneous_apply_refs([&pc](auto& element) { return analysis_task_parsers::prepareDelayedOutput(pc, element); }, *task.get()); + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::prepareDelayedOutput(pc, element); }, *task.get()); // finalize outputs - homogeneous_apply_refs([&pc](auto& element) { return analysis_task_parsers::finalizeOutput(pc, element); }, *task.get()); + homogeneous_apply_refs_sized([&pc](auto& element) { return analysis_task_parsers::finalizeOutput(pc, element); }, *task.get()); }; }}; return { name, - // FIXME: For the moment we hardcode this. We could build - // this list from the list of methods actually implemented in the - // task itself. inputs, outputs, algo, diff --git a/Framework/Core/include/Framework/Array2D.h b/Framework/Core/include/Framework/Array2D.h index 593a50afd91f6..857e4b3c89f29 100644 --- a/Framework/Core/include/Framework/Array2D.h +++ b/Framework/Core/include/Framework/Array2D.h @@ -166,26 +166,26 @@ class LabeledArray : public LabelMap using element_t = T; LabeledArray() - : values{}, - LabelMap{} + : LabelMap{}, + values{} { } LabeledArray(T const* data, uint32_t rows_, uint32_t cols_, std::vector labels_rows_ = {}, std::vector labels_cols_ = {}) - : values{data, rows_, cols_}, - LabelMap{rows_, cols_, labels_rows_, labels_cols_} + : LabelMap{rows_, cols_, labels_rows_, labels_cols_}, + values{data, rows_, cols_} { } LabeledArray(T const* data, uint32_t size, std::vector labels_ = {}) - : values{data, 1, size}, - LabelMap{size, labels_} + : LabelMap{size, labels_}, + values{data, 1, size} { } LabeledArray(Array2D const& data, std::vector labels_rows_ = {}, std::vector labels_cols_ = {}) - : values{data}, - LabelMap{data.rows, data.cols, labels_rows_, labels_cols_} + : LabelMap{data.rows, data.cols, labels_rows_, labels_cols_}, + values{data} { } diff --git a/Framework/Core/include/Framework/ArrowTableSlicingCache.h b/Framework/Core/include/Framework/ArrowTableSlicingCache.h index 40991a955e52b..073eadc22d72c 100644 --- a/Framework/Core/include/Framework/ArrowTableSlicingCache.h +++ b/Framework/Core/include/Framework/ArrowTableSlicingCache.h @@ -12,6 +12,7 @@ #ifndef ARROWTABLESLICINGCACHE_H #define ARROWTABLESLICINGCACHE_H +#include "Framework/ConcreteDataMatcher.h" #include "Framework/ServiceHandle.h" #include #include @@ -36,20 +37,28 @@ struct SliceInfoUnsortedPtr { struct Entry { std::string binding; + ConcreteDataMatcher matcher; std::string key; bool enabled; - Entry(std::string b, std::string k, bool e = true) + Entry(std::string b, ConcreteDataMatcher m, std::string k, bool e = true) : binding{b}, + matcher{m}, key{k}, enabled{e} { } + + friend bool operator==(Entry const& lhs, Entry const& rhs) + { + return (lhs.matcher == rhs.matcher) && + (lhs.key == rhs.key); + } }; using Cache = std::vector; -void updatePairList(Cache& list, std::string const& binding, std::string const& key, bool enabled); +void updatePairList(Cache& list, Entry& entry); struct ArrowTableSlicingCacheDef { constexpr static ServiceKind service_kind = ServiceKind::Global; @@ -64,8 +73,6 @@ struct ArrowTableSlicingCache { constexpr static ServiceKind service_kind = ServiceKind::Stream; Cache bindingsKeys; - std::vector>> values; - std::vector>> counts; std::vector> offsets; std::vector> sizes; diff --git a/Framework/Core/include/Framework/ArrowTypes.h b/Framework/Core/include/Framework/ArrowTypes.h index 6fd70113fede7..2673472a81152 100644 --- a/Framework/Core/include/Framework/ArrowTypes.h +++ b/Framework/Core/include/Framework/ArrowTypes.h @@ -11,6 +11,7 @@ #ifndef O2_FRAMEWORK_ARROWTYPES_H #define O2_FRAMEWORK_ARROWTYPES_H +#include "Framework/Traits.h" #include "arrow/type_fwd.h" #include @@ -117,5 +118,54 @@ template using arrow_array_for_t = typename arrow_array_for::type; template using value_for_t = typename arrow_array_for::value_type; + +template +using array_element_t = std::decay_t()[0])>; + +template +std::shared_ptr asArrowDataType(int list_size = 1) +{ + auto typeGenerator = [](std::shared_ptr const& type, int list_size) -> std::shared_ptr { + switch (list_size) { + case -1: + return arrow::list(type); + case 1: + return std::move(type); + default: + return arrow::fixed_size_list(type, list_size); + } + }; + + if constexpr (std::is_arithmetic_v) { + if constexpr (std::same_as) { + return typeGenerator(arrow::boolean(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::uint8(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::uint16(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::uint32(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::uint64(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::int8(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::int16(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::int32(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::int64(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::float32(), list_size); + } else if constexpr (std::same_as) { + return typeGenerator(arrow::float64(), list_size); + } + } else if constexpr (std::is_bounded_array_v) { + return asArrowDataType>(std::extent_v); + } else if constexpr (o2::framework::is_specialization_v) { + return asArrowDataType(-1); + } + return nullptr; +} } // namespace o2::soa #endif // O2_FRAMEWORK_ARROWTYPES_H 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/CommonDataProcessors.h b/Framework/Core/include/Framework/CommonDataProcessors.h index 824386c4d5921..48e240c59e5d2 100644 --- a/Framework/Core/include/Framework/CommonDataProcessors.h +++ b/Framework/Core/include/Framework/CommonDataProcessors.h @@ -37,7 +37,11 @@ struct CommonDataProcessors { /// and simply discards them. @a rateLimitingChannelConfig is the configuration /// for the rate limiting channel, if any required. static DataProcessorSpec getDummySink(std::vector const& danglingInputs, std::string rateLimitingChannelConfig); + /// @return a dummy DataProcessorSpec which requires all the passed @a InputSpec + /// and simply discards them. Rate limiting goes through the DPL driver + static DataProcessorSpec getScheduledDummySink(std::vector const& danglingInputs); static AlgorithmSpec wrapWithRateLimiting(AlgorithmSpec spec); + static AlgorithmSpec wrapWithTimesliceConsumption(AlgorithmSpec spec); }; } // namespace o2::framework diff --git a/Framework/Core/include/Framework/CommonLabels.h b/Framework/Core/include/Framework/CommonLabels.h new file mode 100644 index 0000000000000..8be41a33af41d --- /dev/null +++ b/Framework/Core/include/Framework/CommonLabels.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_FRAMEWORK_COMMONLABELS_H +#define O2_FRAMEWORK_COMMONLABELS_H + +#include "Framework/DataProcessorLabel.h" + +namespace o2::framework +{ + +// Label to disable forwarding/advertising of DomainInfoHeader (oldest possible outputs) +// When present on a DataProcessor, no DomainInfoHeader messages will be sent downstream. +const extern DataProcessorLabel suppressDomainInfoLabel; + +} // namespace o2::framework + +#endif // O2_FRAMEWORK_COMMONLABELS_H diff --git a/Framework/Core/include/Framework/CommonServices.h b/Framework/Core/include/Framework/CommonServices.h index 69f3152c0ba76..f5080fcac28db 100644 --- a/Framework/Core/include/Framework/CommonServices.h +++ b/Framework/Core/include/Framework/CommonServices.h @@ -56,7 +56,6 @@ struct CommonServices { return [](InitContext&, void* service) -> void* { return service; }; } - static ServiceSpec deviceContextSpec(); static ServiceSpec dataProcessorContextSpec(); static ServiceSpec driverClientSpec(); static ServiceSpec monitoringSpec(); diff --git a/Framework/Core/include/Framework/CompletionPolicyHelpers.h b/Framework/Core/include/Framework/CompletionPolicyHelpers.h index aa336d040d30d..09ea8b7ea6b61 100644 --- a/Framework/Core/include/Framework/CompletionPolicyHelpers.h +++ b/Framework/Core/include/Framework/CompletionPolicyHelpers.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. // @@ -11,7 +11,6 @@ #ifndef O2_FRAMEWORK_COMPLETIONPOLICYHELPERS_H_ #define O2_FRAMEWORK_COMPLETIONPOLICYHELPERS_H_ -#include "Framework/ChannelSpec.h" #include "Framework/CompletionPolicy.h" #include "Headers/DataHeader.h" @@ -44,10 +43,9 @@ struct CompletionPolicyHelpers { /// When any of the parts of the record have been received, consume them. static CompletionPolicy consumeWhenAny(const char* name, CompletionPolicy::Matcher matcher); -#if __has_include() /// When any of the parts which has arrived has a refcount of 1. static CompletionPolicy consumeWhenAnyZeroCount(const char* name, CompletionPolicy::Matcher matcher); -#endif + /// Default matcher applies for all devices static CompletionPolicy consumeWhenAny(CompletionPolicy::Matcher matcher = [](auto const&) -> bool { return true; }) { @@ -55,6 +53,12 @@ struct CompletionPolicyHelpers { } static CompletionPolicy consumeWhenAny(std::string matchName); + // Consume all the data captured until the oldest possible timeframe + // in input indicates that nothing else can be added to this timeslice. + // Useful in case of wildcards which multiplex multiple subspecs on the + // same input. + static CompletionPolicy consumeWhenPastOldestPossibleTimeframe(const char* name, CompletionPolicy::Matcher matcher); + /// When any of the parts of the record have been received, consume them. static CompletionPolicy consumeWhenAnyWithAllConditions(const char* name, CompletionPolicy::Matcher matcher); /// Default matcher applies for all devices diff --git a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h index aee2e50c90e7f..b25bc1611d79f 100644 --- a/Framework/Core/include/Framework/ComputingQuotaEvaluator.h +++ b/Framework/Core/include/Framework/ComputingQuotaEvaluator.h @@ -32,12 +32,14 @@ class ComputingQuotaEvaluator { public: // Maximum number of offers this evaluator can hold - static constexpr int MAX_INFLIGHT_OFFERS = 16; + static constexpr int MAX_INFLIGHT_OFFERS = 32; ComputingQuotaEvaluator(ServiceRegistryRef ref); /// @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/ComputingQuotaOffer.h b/Framework/Core/include/Framework/ComputingQuotaOffer.h index f457f46eef774..8d4420e7af2f5 100644 --- a/Framework/Core/include/Framework/ComputingQuotaOffer.h +++ b/Framework/Core/include/Framework/ComputingQuotaOffer.h @@ -44,6 +44,8 @@ struct ComputingQuotaOffer { int64_t memory = 0; /// How much shared memory it can allocate int64_t sharedMemory = 0; + /// How many timeslices it can process without giving back control + int64_t timeslices = 0; /// How much runtime it can use before giving back the resource /// in milliseconds. int64_t runtime = 0; @@ -68,8 +70,10 @@ struct ComputingQuotaInfo { /// Statistics on the offers consumed, expired struct ComputingQuotaStats { int64_t totalConsumedBytes = 0; + int64_t totalConsumedTimeslices = 0; int64_t totalConsumedOffers = 0; int64_t totalExpiredBytes = 0; + int64_t totalExpiredTimeslices = 0; int64_t totalExpiredOffers = 0; }; @@ -80,7 +84,7 @@ using ComputingQuotaRequest = std::function&, ComputingQuotaStats&, std::function)>; +using ComputingQuotaConsumer = std::function&, ComputingQuotaStats&, std::function)>; } // namespace o2::framework diff --git a/Framework/Core/include/Framework/ConcreteDataMatcher.h b/Framework/Core/include/Framework/ConcreteDataMatcher.h index 247e3cd6ed8b9..bfbd2a05a8709 100644 --- a/Framework/Core/include/Framework/ConcreteDataMatcher.h +++ b/Framework/Core/include/Framework/ConcreteDataMatcher.h @@ -56,9 +56,9 @@ struct ConcreteDataMatcher { header::DataDescription description; header::DataHeader::SubSpecificationType subSpec; - ConcreteDataMatcher(header::DataOrigin origin_, - header::DataDescription description_, - header::DataHeader::SubSpecificationType subSpec_) + constexpr ConcreteDataMatcher(header::DataOrigin origin_, + header::DataDescription description_, + header::DataHeader::SubSpecificationType subSpec_) : origin(origin_), description(description_), subSpec(subSpec_) diff --git a/Framework/Core/include/Framework/Configurable.h b/Framework/Core/include/Framework/Configurable.h index f72d2f3a2a7d6..3cbd1839b7d89 100644 --- a/Framework/Core/include/Framework/Configurable.h +++ b/Framework/Core/include/Framework/Configurable.h @@ -83,40 +83,58 @@ 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) { - typename T::type; +concept is_configurable = requires(T t) { requires std::same_as; - &T::operator typename T::type; + requires std::same_as; + requires std::same_as::type, decltype(t.value)>; }; 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 +template struct ProcessConfigurable : Configurable { - ProcessConfigurable(R (T::*process_)(As...), std::string const& name_, bool&& value_, std::string const& help_) + ProcessConfigurable(void (T::*process_)(As...), std::string const& name_, bool&& value_, std::string const& help_) : process{process_}, Configurable(name_, std::forward(value_), help_) { } - R(T::*process) - (As...); + void (T::*process)(As...); }; template -concept is_process_configurable = is_configurable && requires(T& t) { t.process; }; +concept is_process_configurable = is_configurable && requires(T t) { t.process; }; #define PROCESS_SWITCH(_Class_, _Name_, _Help_, _Default_) \ - decltype(ProcessConfigurable{&_Class_ ::_Name_, #_Name_, _Default_, _Help_}) do##_Name_ = ProcessConfigurable{&_Class_ ::_Name_, #_Name_, _Default_, _Help_}; + decltype(o2::framework::ProcessConfigurable{&_Class_ ::_Name_, #_Name_, _Default_, _Help_}) do##_Name_ = o2::framework::ProcessConfigurable{&_Class_ ::_Name_, #_Name_, _Default_, _Help_}; #define PROCESS_SWITCH_FULL(_Class_, _Method_, _Name_, _Help_, _Default_) \ - decltype(ProcessConfigurable{&_Class_ ::_Method_, #_Name_, _Default_, _Help_}) do##_Name_ = ProcessConfigurable{&_Class_ ::_Method_, #_Name_, _Default_, _Help_}; + decltype(o2::framework::ProcessConfigurable{&_Class_ ::_Method_, #_Name_, _Default_, _Help_}) do##_Name_ = o2::framework::ProcessConfigurable{&_Class_ ::_Method_, #_Name_, _Default_, _Help_}; template std::ostream& operator<<(std::ostream& os, Configurable const& c) diff --git a/Framework/Core/include/Framework/AnalysisContext.h b/Framework/Core/include/Framework/DanglingEdgesContext.h similarity index 82% rename from Framework/Core/include/Framework/AnalysisContext.h rename to Framework/Core/include/Framework/DanglingEdgesContext.h index 7d1544ed312a4..c5f54297ee746 100644 --- a/Framework/Core/include/Framework/AnalysisContext.h +++ b/Framework/Core/include/Framework/DanglingEdgesContext.h @@ -8,8 +8,8 @@ // 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_ANALYSISCONTEXT_H_ -#define O2_FRAMEWORK_ANALYSISCONTEXT_H_ +#ifndef O2_FRAMEWORK_DANGLINGEDGESCONTEXT_H_ +#define O2_FRAMEWORK_DANGLINGEDGESCONTEXT_H_ #include #include "Framework/InputSpec.h" @@ -32,16 +32,25 @@ struct OutputObjectInfo { // This will keep track of the inputs which have // been requested and for which we will need to inject // some source device. -struct AnalysisContext { +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. @@ -63,4 +72,4 @@ struct AnalysisContext { extern template class std::vector; extern template class std::vector; -#endif // O2_FRAMEWORK_ANALYSISCONTEXT_H_ +#endif // O2_FRAMEWORK_DANGLINGEDGESCONTEXT_H_ diff --git a/Framework/Core/include/Framework/DataAllocator.h b/Framework/Core/include/Framework/DataAllocator.h index 287513ec85845..ed9a31ca2857c 100644 --- a/Framework/Core/include/Framework/DataAllocator.h +++ b/Framework/Core/include/Framework/DataAllocator.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. // @@ -29,9 +29,9 @@ #include "Headers/DataHeader.h" #include -#include #include +#include #include #include #include @@ -127,6 +127,10 @@ template concept VectorOfMessageableTypes = is_specialization_v && is_messageable::value; +template +concept ContiguousMessageablesRange = std::ranges::contiguous_range && + is_messageable::value; + /// This allocator is responsible to make sure that the messages created match /// the provided spec and that depending on how many pipelined reader we /// have, messages get created on the channel for the reader of the current @@ -296,8 +300,9 @@ class DataAllocator /// /// Supported types: /// - messageable types (trivially copyable, non-polymorphic - /// - std::vector of messageable types - /// - std::vector of pointers of messageable type + /// - contiguous_range of messageable types + /// - random_access_ranges of pointers of messageable type + /// - sized range of messageable type /// - types with ROOT dictionary and implementing the ROOT ClassDef interface /// /// Note: for many use cases, especially for the messageable types, the `make` interface @@ -308,116 +313,140 @@ class DataAllocator /// Use @a ROOTSerialized type wrapper to force ROOT serialization. Same applies to /// types which do not implement the ClassDef interface but have a dictionary. template + requires(!std::ranges::contiguous_range && is_messageable::value) + void snapshot(const Output& spec, T const& object) + { + return snapshot(spec, std::span(&object, &object + 1)); + } + + void snapshot(const Output& spec, std::string_view const& object) + { + return snapshot(spec, std::span(object.data(), object.size())); + } + + // This is for snapshotting a range of contiguous messageable types + template + requires(ContiguousMessageablesRange && !std::is_pointer_v) void snapshot(const Output& spec, T const& object) { auto& proxy = mRegistry.get().proxy(); - fair::mq::MessagePtr payloadMessage; - auto serializationType = o2::header::gSerializationMethodNone; RouteIndex routeIndex = matchDataHeader(spec, mRegistry.get().timeslice); - if constexpr (is_messageable::value == true) { - // Serialize a snapshot of a trivially copyable, non-polymorphic object, - payloadMessage = proxy.createOutputMessage(routeIndex, sizeof(T)); - memcpy(payloadMessage->GetData(), &object, sizeof(T)); - - serializationType = o2::header::gSerializationMethodNone; - } else if constexpr (is_specialization_v == true || - (gsl::details::is_span::value && has_messageable_value_type::value)) { - using ElementType = typename std::remove_pointer::type; - if constexpr (is_messageable::value) { - // Serialize a snapshot of a std::vector of trivially copyable, non-polymorphic elements - // Note: in most cases it is better to use the `make` function und work with the provided - // reference object - constexpr auto elementSizeInBytes = sizeof(ElementType); - auto sizeInBytes = elementSizeInBytes * object.size(); - payloadMessage = proxy.createOutputMessage(routeIndex, sizeInBytes); - - if constexpr (std::is_pointer::value == false) { - // vector of elements - if (object.data() && sizeInBytes) { - memcpy(payloadMessage->GetData(), object.data(), sizeInBytes); - } - } else { - // serialize vector of pointers to elements - auto target = reinterpret_cast(payloadMessage->GetData()); - for (auto const& pointer : object) { - memcpy(target, pointer, elementSizeInBytes); - target += elementSizeInBytes; - } - } - - serializationType = o2::header::gSerializationMethodNone; - } else if constexpr (has_root_dictionary::value) { - return snapshot(spec, ROOTSerialized(object)); - } else { - static_assert(always_static_assert_v, - "value type of std::vector not supported by API, supported types:" - "\n - messageable tyeps (trivially copyable, non-polymorphic structures)" - "\n - pointers to those" - "\n - types with ROOT dictionary and implementing ROOT ClassDef interface"); - } - } else if constexpr (is_container::value == true && has_messageable_value_type::value == true) { - // Serialize a snapshot of a std::container of trivially copyable, non-polymorphic elements - // Note: in most cases it is better to use the `make` function und work with the provided - // reference object - constexpr auto elementSizeInBytes = sizeof(typename T::value_type); - auto sizeInBytes = elementSizeInBytes * object.size(); - payloadMessage = proxy.createOutputMessage(routeIndex, sizeInBytes); - - // serialize vector of pointers to elements - auto target = reinterpret_cast(payloadMessage->GetData()); - for (auto const& entry : object) { - memcpy(target, (void*)&entry, elementSizeInBytes); - target += elementSizeInBytes; - } - serializationType = o2::header::gSerializationMethodNone; - } else if constexpr (has_root_dictionary::value == true || is_specialization_v == true) { - // Serialize a snapshot of an object with root dictionary - payloadMessage = proxy.createOutputMessage(routeIndex); - payloadMessage->Rebuild(4096, {64}); - if constexpr (is_specialization_v == true) { - // Explicitely ROOT serialize a snapshot of object. - // An object wrapped into type `ROOTSerialized` is explicitely marked to be ROOT serialized - // and is expected to have a ROOT dictionary. Availability can not be checked at compile time - // for all cases. - using WrappedType = typename T::wrapped_type; - static_assert(std::is_same::value || - std::is_same::value || - std::is_void::value, - "class hint must be of type TClass or const char"); - - const TClass* cl = nullptr; - if (object.getHint() == nullptr) { - // get TClass info by wrapped type - cl = TClass::GetClass(typeid(WrappedType)); - } else if (std::is_same::value) { - // the class info has been passed directly - cl = reinterpret_cast(object.getHint()); - } else if (std::is_same::value) { - // get TClass info by optional name - cl = TClass::GetClass(reinterpret_cast(object.getHint())); - } - if (has_root_dictionary::value == false && cl == nullptr) { - if (std::is_same::value) { - throw runtime_error_f("ROOT serialization not supported, dictionary not found for type %s", - reinterpret_cast(object.getHint())); - } else { - throw runtime_error_f("ROOT serialization not supported, dictionary not found for type %s", - typeid(WrappedType).name()); - } - } - typename root_serializer::serializer().Serialize(*payloadMessage, &object(), cl); + using ElementType = typename std::remove_pointer::type; + // Serialize a snapshot of a std::vector of trivially copyable, non-polymorphic elements + // Note: in most cases it is better to use the `make` function und work with the provided + // reference object + constexpr auto elementSizeInBytes = sizeof(ElementType); + auto sizeInBytes = elementSizeInBytes * object.size(); + fair::mq::MessagePtr payloadMessage = proxy.createOutputMessage(routeIndex, sizeInBytes); + + // vector of elements + if (object.data() && sizeInBytes) { + memcpy(payloadMessage->GetData(), object.data(), sizeInBytes); + } + + addPartToContext(routeIndex, std::move(payloadMessage), spec, o2::header::gSerializationMethodNone); + } + + // A random access range of pointers we can serialise by storing the contens one after the other. + // On the receiving side you will have to retrieve it via a span + template + requires(std::ranges::random_access_range && is_messageable>::value && std::is_pointer_v) + void snapshot(const Output& spec, T const& object) + { + auto& proxy = mRegistry.get().proxy(); + RouteIndex routeIndex = matchDataHeader(spec, mRegistry.get().timeslice); + using ElementType = typename std::remove_pointer_t; + // Serialize a snapshot of a std::vector of trivially copyable, non-polymorphic elements + // Note: in most cases it is better to use the `make` function und work with the provided + // reference object + constexpr auto elementSizeInBytes = sizeof(ElementType); + auto sizeInBytes = elementSizeInBytes * object.size(); + fair::mq::MessagePtr payloadMessage = proxy.createOutputMessage(routeIndex, sizeInBytes); + + // serialize vector of pointers to elements + auto target = reinterpret_cast(payloadMessage->GetData()); + for (auto const& pointer : object) { + memcpy(target, pointer, elementSizeInBytes); + target += elementSizeInBytes; + } + + addPartToContext(routeIndex, std::move(payloadMessage), spec, o2::header::gSerializationMethodNone); + } + + // This is for a range where we can know upfront how many elements there are, + // so that we can preallocate the final size by simply multipling sizeof(T) x N elements + template + requires(!std::ranges::contiguous_range && std::ranges::sized_range && has_messageable_value_type::value) + void snapshot(const Output& spec, T const& object) + { + auto& proxy = mRegistry.get().proxy(); + RouteIndex routeIndex = matchDataHeader(spec, mRegistry.get().timeslice); + // Serialize a snapshot of a std::container of trivially copyable, non-polymorphic elements + // Note: in most cases it is better to use the `make` function und work with the provided + // reference object + constexpr auto elementSizeInBytes = sizeof(typename T::value_type); + auto sizeInBytes = elementSizeInBytes * object.size(); + fair::mq::MessagePtr payloadMessage = proxy.createOutputMessage(routeIndex, sizeInBytes); + + // serialize vector of pointers to elements + auto target = reinterpret_cast(payloadMessage->GetData()); + for (auto const& entry : object) { + memcpy(target, (void*)&entry, elementSizeInBytes); + target += elementSizeInBytes; + } + addPartToContext(routeIndex, std::move(payloadMessage), spec, o2::header::gSerializationMethodNone); + } + + template + requires(is_specialization_v) + void snapshot(const Output& spec, T const& object) + { + auto& proxy = mRegistry.get().proxy(); + RouteIndex routeIndex = matchDataHeader(spec, mRegistry.get().timeslice); + // Serialize a snapshot of an object with root dictionary + fair::mq::MessagePtr payloadMessage = proxy.createOutputMessage(routeIndex); + payloadMessage->Rebuild(4096, {64}); + const TClass* cl = nullptr; + // Explicitely ROOT serialize a snapshot of object. + // An object wrapped into type `ROOTSerialized` is explicitely marked to be ROOT serialized + // and is expected to have a ROOT dictionary. Availability can not be checked at compile time + // for all cases. + using WrappedType = typename T::wrapped_type; + + if (object.getHint() == nullptr) { + // get TClass info by wrapped type + cl = TClass::GetClass(typeid(WrappedType)); + } else if (std::is_same::value) { + // the class info has been passed directly + cl = reinterpret_cast(object.getHint()); + } else if (std::is_same::value) { + // get TClass info by optional name + cl = TClass::GetClass(reinterpret_cast(object.getHint())); + } + if (has_root_dictionary::value == false && cl == nullptr) { + if (std::is_same::value) { + throw runtime_error_f("ROOT serialization not supported, dictionary not found for type %s", + reinterpret_cast(object.getHint())); } else { - typename root_serializer::serializer().Serialize(*payloadMessage, &object, TClass::GetClass(typeid(T))); + throw runtime_error_f("ROOT serialization not supported, dictionary not found for type %s", + typeid(WrappedType).name()); } - serializationType = o2::header::gSerializationMethodROOT; - } else { - static_assert(always_static_assert_v, - "data type T not supported by API, \n specializations available for" - "\n - trivially copyable, non-polymorphic structures" - "\n - std::vector of messageable structures or pointers to those" - "\n - types with ROOT dictionary and implementing ROOT ClassDef interface"); } - addPartToContext(routeIndex, std::move(payloadMessage), spec, serializationType); + typename root_serializer::serializer().Serialize(*payloadMessage, &object(), cl); + addPartToContext(routeIndex, std::move(payloadMessage), spec, o2::header::gSerializationMethodROOT); + } + + template + requires(!is_messageable::value && !ContiguousMessageablesRange && has_root_dictionary::value && !is_specialization_v) + void snapshot(const Output& spec, T const& object) + { + auto& proxy = mRegistry.get().proxy(); + RouteIndex routeIndex = matchDataHeader(spec, mRegistry.get().timeslice); + // Serialize a snapshot of an object with root dictionary + fair::mq::MessagePtr payloadMessage = proxy.createOutputMessage(routeIndex); + payloadMessage->Rebuild(4096, {64}); + typename root_serializer::serializer().Serialize(*payloadMessage, &object, TClass::GetClass(typeid(T))); + addPartToContext(routeIndex, std::move(payloadMessage), spec, o2::header::gSerializationMethodROOT); } /// Take a snapshot of a raw data array which can be either POD or may contain a serialized diff --git a/Framework/Core/include/Framework/DataModelViews.h b/Framework/Core/include/Framework/DataModelViews.h new file mode 100644 index 0000000000000..dd8d65ea16459 --- /dev/null +++ b/Framework/Core/include/Framework/DataModelViews.h @@ -0,0 +1,288 @@ +// Copyright 2019-2025 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_DATASPECVIEWS_H_ +#define O2_FRAMEWORK_DATASPECVIEWS_H_ + +#include +#include +#include "DomainInfoHeader.h" +#include "SourceInfoHeader.h" +#include "Headers/DataHeader.h" +#include "Framework/DataRef.h" +#include "Framework/TimesliceSlot.h" +#include +#include + +namespace o2::framework +{ + +struct count_payloads { + // ends the pipeline, returns the container + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend size_t operator|(R&& r, count_payloads self) + { + size_t count = 0; + size_t mi = 0; + while (mi < r.size()) { + auto* header = o2::header::get(r[mi]->GetData()); + if (!header) { + throw std::runtime_error("Not a DataHeader"); + } + if (header->splitPayloadParts > 1 && header->splitPayloadIndex == header->splitPayloadParts) { + count += header->splitPayloadParts; + mi += header->splitPayloadParts + 1; + } else { + count += header->splitPayloadParts ? header->splitPayloadParts : 1; + mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; + } + } + return count; + } +}; + +struct count_parts { + // ends the pipeline, returns the number of parts + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend size_t operator|(R&& r, count_parts self) + { + size_t count = 0; + size_t mi = 0; + while (mi < r.size()) { + auto* header = o2::header::get(r[mi]->GetData()); + auto* sih = o2::header::get(r[mi]->GetData()); + auto* dih = o2::header::get(r[mi]->GetData()); + if (!header && !sih && !dih) { + throw std::runtime_error("Header information not found"); + } + // We skip oldest possible timeframe / end of stream and not consider it + // as actual parts. + if (dih || sih) { + count += 1; + mi += 2; + } else if (header->splitPayloadParts > 1 && header->splitPayloadIndex == header->splitPayloadParts) { + count += 1; + mi += header->splitPayloadParts + 1; + } else { + count += header->splitPayloadParts ? header->splitPayloadParts : 1; + mi += header->splitPayloadParts ? 2 * header->splitPayloadParts : 2; + } + } + return count; + } +}; + +// DataRefIndices is defined in Framework/DataRef.h + +struct get_pair { + size_t pairId; + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend DataRefIndices operator|(R&& r, get_pair self) + { + size_t count = 0; + size_t mi = 0; + while (mi < r.size()) { + auto* header = o2::header::get(r[mi]->GetData()); + if (!header) { + throw std::runtime_error("Not a DataHeader"); + } + 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 { + // Single [header, payload] pair (splitPayloadParts == 0). + if (self.pairId == count) { + return {mi, mi + 1}; + } + 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; + // ends the pipeline, returns the number of parts + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend DataRefIndices operator|(R&& r, get_dataref_indices self) + { + size_t count = 0; + size_t mi = 0; + while (mi < r.size()) { + auto* header = o2::header::get(r[mi]->GetData()); + if (!header) { + throw std::runtime_error("Not a DataHeader"); + } + if (header->splitPayloadParts > 1 && header->splitPayloadIndex == header->splitPayloadParts) { + if (self.part == count) { + return {mi, mi + 1 + self.subPart}; + } + count += 1; + mi += header->splitPayloadParts + 1; + } else { + if (self.part == count) { + return {mi, mi + self.subPart + 1}; + } + count += 1; + mi += 2; + } + } + throw std::runtime_error("Payload not found"); + } +}; + +struct get_header { + size_t id; + // ends the pipeline, returns the number of parts + template + requires std::ranges::random_access_range && std::ranges::sized_range + friend auto& operator|(R&& r, get_header self) + { + return r[(r | get_dataref_indices{self.id, 0}).headerIdx]; + } +}; + +struct get_payload { + size_t part; + size_t subPart; + // ends the pipeline, returns the number of parts + template + requires std::ranges::random_access_range && std::ranges::sized_range + 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 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 (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; + } + // For multipayload we skip all the parts and their associated header + count += 1; + mi += header->splitPayloadParts + 1; + } else { + // 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 inputs_for_slot { + TimesliceSlot slot; + template + 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]); + } +}; + +struct messages_for_input { + size_t inputIdx; + template + requires std::ranges::random_access_range + friend std::span operator|(R&& r, messages_for_input self) + { + return std::span(r[self.inputIdx]); + } +}; + +// FIXME: we should use special index classes in place of size_t +// FIXME: we need something to substitute a range in the store with another + +} // namespace o2::framework + +#endif // O2_FRAMEWORK_DATASPECVIEWS_H_ diff --git a/Framework/Core/include/Framework/DataProcessingContext.h b/Framework/Core/include/Framework/DataProcessingContext.h index 9b7cbc238c942..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 @@ -23,11 +24,17 @@ struct ServiceRegistry; struct DataAllocator; struct DataProcessorSpec; +enum struct ForwardPolicy { + AtInjection, + AtCompletionPolicySatisified, + AfterProcessing +}; + 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; @@ -122,7 +129,7 @@ struct DataProcessorContext { mutable std::vector preLoopHandles; /// Wether or not the associated DataProcessor can forward things early - bool canForwardEarly = true; + ForwardPolicy forwardPolicy = ForwardPolicy::AtInjection; bool isSink = false; bool balancingInputs = true; diff --git a/Framework/Core/include/Framework/DataProcessingDevice.h b/Framework/Core/include/Framework/DataProcessingDevice.h index 67edaa99e532b..b2281274acc87 100644 --- a/Framework/Core/include/Framework/DataProcessingDevice.h +++ b/Framework/Core/include/Framework/DataProcessingDevice.h @@ -77,7 +77,7 @@ struct DeviceConfigurationHelpers { class DataProcessingDevice : public fair::mq::Device { public: - DataProcessingDevice(RunningDeviceRef ref, ServiceRegistry&, ProcessingPolicies& policies); + DataProcessingDevice(RunningDeviceRef ref, ServiceRegistry&); void Init() final; void InitTask() final; void PreRun() final; @@ -112,7 +112,6 @@ class DataProcessingDevice : public fair::mq::Device uint64_t mBeginIterationTimestamp = 0; /// The timestamp of when the current ConditionalRun was started std::vector mPendingRegionInfos; /// A list of the region infos not yet notified. std::mutex mRegionInfoMutex; - ProcessingPolicies mProcessingPolicies; /// User policies related to data processing std::vector mHandles; /// Handles to use to schedule work. std::vector mStreams; /// Information about the task running in the associated mHandle. /// Handle to wake up the main loop from other threads diff --git a/Framework/Core/include/Framework/DataProcessingHelpers.h b/Framework/Core/include/Framework/DataProcessingHelpers.h index 7a05ddf25c46c..f414e3aa4ae00 100644 --- a/Framework/Core/include/Framework/DataProcessingHelpers.h +++ b/Framework/Core/include/Framework/DataProcessingHelpers.h @@ -12,6 +12,12 @@ #define O2_FRAMEWORK_DATAPROCESSINGHELPERS_H_ #include +#include "Framework/TimesliceSlot.h" +#include "Framework/TimesliceIndex.h" +#include +#include +#include +#include namespace o2::framework { @@ -21,6 +27,12 @@ struct ForwardChannelState; struct OutputChannelInfo; struct OutputChannelSpec; struct OutputChannelState; +struct ProcessingPolicies; +struct DeviceSpec; +struct FairMQDeviceProxy; +struct ChannelIndex; +enum struct StreamingState; +enum struct TransitionHandlingState; /// Generic helpers for DataProcessing releated functions. struct DataProcessingHelpers { @@ -35,7 +47,20 @@ struct DataProcessingHelpers { static bool sendOldestPossibleTimeframe(ServiceRegistryRef const& ref, OutputChannelInfo const& info, OutputChannelState& state, size_t timeslice); /// Broadcast the oldest possible timeslice to all channels in output static void broadcastOldestPossibleTimeslice(ServiceRegistryRef const& ref, size_t timeslice); -}; + /// change the device StreamingState to newState + static void switchState(ServiceRegistryRef const& ref, StreamingState newState); + /// check if spec is a source devide + static bool hasOnlyGenerated(DeviceSpec const& spec); + /// 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, + bool copy, bool consume); + /// Helper to route messages for forwarding + static void routeForwardedMessages(FairMQDeviceProxy& proxy, std::span& currentSetOfInputs, std::vector& forwardedParts, + bool copy, bool consume); + static void cleanForwardedMessages(std::span& currentSetOfInputs, bool consume); +}; } // namespace o2::framework #endif // O2_FRAMEWORK_DATAPROCESSINGHELPERS_H_ diff --git a/Framework/Core/include/Framework/DataProcessingStats.h b/Framework/Core/include/Framework/DataProcessingStats.h index d42f9a9d26610..edb04c4c5f752 100644 --- a/Framework/Core/include/Framework/DataProcessingStats.h +++ b/Framework/Core/include/Framework/DataProcessingStats.h @@ -57,14 +57,23 @@ enum struct ProcessingStatsId : short { CPU_USAGE_FRACTION, ARROW_BYTES_CREATED, ARROW_BYTES_DESTROYED, + ARROW_BYTES_EXPIRED, ARROW_MESSAGES_CREATED, ARROW_MESSAGES_DESTROYED, - ARROW_BYTES_EXPIRED, + TIMESLICE_OFFER_NUMBER_CONSUMED, + TIMESLICE_NUMBER_STARTED, + TIMESLICE_NUMBER_EXPIRED, + TIMESLICE_NUMBER_DONE, RESOURCE_OFFER_EXPIRED, SHM_OFFER_BYTES_CONSUMED, RESOURCES_MISSING, RESOURCES_INSUFFICIENT, RESOURCES_SATISFACTORY, + CCDB_CACHE_HIT, + CCDB_CACHE_MISS, + CCDB_CACHE_FAILURE, + CCDB_CACHE_FETCHED_BYTES, + CCDB_CACHE_REQUESTED_BYTES, AVAILABLE_MANAGED_SHM_BASE = 512, }; @@ -170,9 +179,11 @@ struct DataProcessingStats { }; void registerMetric(MetricSpec const& spec); + // Update some stats as specified by the @cmd cmd void updateStats(CommandSpec cmd); + char const* findMetricNameById(ProcessingStatsId id) const; /// This will process the queue of commands required to update the stats. /// It is meant to be called periodically by a single thread. void processCommandQueue(); 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/DataRefUtils.h b/Framework/Core/include/Framework/DataRefUtils.h index d50699badc63b..498b4eb5f7a87 100644 --- a/Framework/Core/include/Framework/DataRefUtils.h +++ b/Framework/Core/include/Framework/DataRefUtils.h @@ -23,6 +23,7 @@ #include +#include #include #include @@ -184,6 +185,10 @@ struct DataRefUtils { // Decode a CCDB object using the CcdbApi. static void* decodeCCDB(DataRef const& ref, std::type_info const& info); static std::map extractCCDBHeaders(DataRef const& ref); + /// Return a span over the raw CCDB payload bytes, stripping the flattened HTTP + /// headers footer that CCDBFetcherHelper appends. Use this when the CCDB entry is + /// a binary blob rather than a ROOT-serialised object. + static std::span getCCDBPayloadBlob(DataRef const& ref); static o2::header::DataHeader::PayloadSizeType getPayloadSize(const DataRef& ref) { diff --git a/Framework/Core/include/Framework/DataRelayer.h b/Framework/Core/include/Framework/DataRelayer.h index 012b909096317..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" @@ -102,7 +102,8 @@ class DataRelayer DataRelayer(CompletionPolicy const&, std::vector const& routes, TimesliceIndex&, - ServiceRegistryRef); + ServiceRegistryRef, + int); /// This invokes the appropriate `InputRoute::danglingChecker` on every /// entry in the cache and if it returns true, it creates a new @@ -112,7 +113,10 @@ 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&)>; /// Prune all the pending entries in the cache. void prunePending(OnDropCallback); @@ -135,6 +139,7 @@ class DataRelayer InputInfo const& info, size_t nMessages, size_t nPayloads = 1, + OnInsertionCallback onInsertion = nullptr, OnDropCallback onDrop = nullptr); /// This is to set the oldest possible @a timeslice this relayer can @@ -151,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; @@ -198,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/DataSpecUtils.h b/Framework/Core/include/Framework/DataSpecUtils.h index 588aa30da7e08..fe322334a8edb 100644 --- a/Framework/Core/include/Framework/DataSpecUtils.h +++ b/Framework/Core/include/Framework/DataSpecUtils.h @@ -127,6 +127,9 @@ struct DataSpecUtils { /// unique way a description should be done, so we keep this outside. static std::string describe(OutputSpec const& spec); + /// Describes a ConcreteDataMatcher + static std::string describe(ConcreteDataMatcher const& matcher); + /// Provide a unique label for the input spec. Again this is outside because there /// is no standard way of doing it, so better not to pollute the API. static std::string label(InputSpec const& spec); @@ -211,6 +214,9 @@ struct DataSpecUtils { /// Create an InputSpec from metadata string static InputSpec fromMetadataString(std::string s); + /// Create a concrete data matcher from serialized string + static ConcreteDataMatcher fromString(std::string s); + /// Get the origin, if available static std::optional getOptionalOrigin(InputSpec const& spec); diff --git a/Framework/Core/include/Framework/DataSpecViews.h b/Framework/Core/include/Framework/DataSpecViews.h index 0782cefd0f632..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); }); @@ -31,6 +54,39 @@ static auto filter_not_matching(auto const& provided) return std::views::filter([&provided](auto const& input) { return std::none_of(provided.begin(), provided.end(), [&input](auto const& output) { return DataSpecUtils::match(input, output); }); }); } +static auto filter_matching(auto const& provided) +{ + return std::views::filter([&provided](auto const& input) { return std::any_of(provided.begin(), provided.end(), [&input](auto const& output) { return DataSpecUtils::match(input, output); }); }); +} + +static auto filter_string_params_with(std::string match) +{ + return std::views::filter([match](auto const& param) { + return (param.type == VariantType::String) && (param.name.find(match) != std::string::npos); + }); +} + +static auto filter_string_params_starts_with(std::string match) +{ + return std::views::filter([match](auto const& param) { + return (param.type == VariantType::String) && (param.name.starts_with(match)); + }); +} + +static auto input_to_output_specs() +{ + return std::views::transform([](auto const& input) { + auto concrete = DataSpecUtils::asConcreteDataMatcher(input); + return OutputSpec{concrete.origin, concrete.description, concrete.subSpec, input.lifetime, input.metadata}; + }); +} + +static auto params_to_input_specs() +{ + return std::views::transform([](auto const& param) { + return DataSpecUtils::fromMetadataString(param.defaultValue.template get()); + }); +} } // namespace o2::framework::views // namespace o2::framework::sinks @@ -54,7 +110,7 @@ struct update_input_list { template friend Container& operator|(R&& r, update_input_list self) { - for (auto& item : r) { + for (auto const& item : r) { auto copy = item; DataSpecUtils::updateInputList(self.c, std::move(copy)); } @@ -62,6 +118,21 @@ struct update_input_list { } }; +template +struct update_output_list { + Container& c; + // ends the pipeline, returns the container + template + friend Container& operator|(R&& r, update_output_list self) + { + for (auto const& item : r) { + auto copy = item; + DataSpecUtils::updateOutputList(self.c, std::move(copy)); + } + return self.c; + } +}; + } // namespace o2::framework::sinks #endif // O2_FRAMEWORK_DATASPECVIEWS_H_ diff --git a/Framework/Core/include/Framework/DataTypes.h b/Framework/Core/include/Framework/DataTypes.h index e273a78f8d0a2..3d49d6d3c03d0 100644 --- a/Framework/Core/include/Framework/DataTypes.h +++ b/Framework/Core/include/Framework/DataTypes.h @@ -51,6 +51,8 @@ enum TrackFlags : uint32_t { OrphanTrack = 0x4, // Track has no association with any collision vertex TrackTimeAsym = 0x8, // track with an asymmetric time range TPCdEdxAlt = 0x10, // TPCSignal and tpcNClsFindableMinusPID correspond for alternative dEdx since the nominal was 0 + TPCSideA = 0x20, // TPC track has A-side clusters (if any) + TPCSideC = 0x40, // TPC track has C-side clusters (if any) // NOTE Highest 4 (29..32) bits reserved for PID hypothesis }; enum TrackFlagsRun2Enum { diff --git a/Framework/Core/include/Framework/DefaultsHelpers.h b/Framework/Core/include/Framework/DefaultsHelpers.h index 16d41d03baa7f..68e64cc42a90e 100644 --- a/Framework/Core/include/Framework/DefaultsHelpers.h +++ b/Framework/Core/include/Framework/DefaultsHelpers.h @@ -12,16 +12,24 @@ #ifndef O2_FRAMEWORK_DEFAULTHELPERS_H_ #define O2_FRAMEWORK_DEFAULTHELPERS_H_ +namespace fair::mq +{ +class ProgOptions; +} + namespace o2::framework { enum struct DeploymentMode; +struct DeviceConfig; struct DefaultsHelpers { static DeploymentMode deploymentMode(); /// @true if running online static bool onlineDeploymentMode(); /// get max number of timeslices in the queue - static unsigned int pipelineLength(); + static unsigned int pipelineLength(unsigned int minLength); + static unsigned int pipelineLength(const fair::mq::ProgOptions& options); + static unsigned int pipelineLength(const DeviceConfig& dc); }; } // namespace o2::framework diff --git a/Framework/Core/include/Framework/DeviceContext.h b/Framework/Core/include/Framework/DeviceContext.h index 4593e5e819ccf..a392004c2ffbf 100644 --- a/Framework/Core/include/Framework/DeviceContext.h +++ b/Framework/Core/include/Framework/DeviceContext.h @@ -21,6 +21,7 @@ typedef struct uv_signal_s uv_signal_t; namespace o2::framework { struct ComputingQuotaStats; +struct ProcessingPolicies; /// Stucture which holds the whole runtime context /// of a running device which is not stored as @@ -33,6 +34,7 @@ struct DeviceContext { int expectedRegionCallbacks = 0; int exitTransitionTimeout = 0; int dataProcessingTimeout = 0; + ProcessingPolicies& processingPolicies; }; } // namespace o2::framework 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/DeviceMetricsInfo.h b/Framework/Core/include/Framework/DeviceMetricsInfo.h index ad143b7ef9373..05249898c65a2 100644 --- a/Framework/Core/include/Framework/DeviceMetricsInfo.h +++ b/Framework/Core/include/Framework/DeviceMetricsInfo.h @@ -154,11 +154,11 @@ struct DeviceMetricsInfo { std::vector> stringMetrics; // We do not keep so many strings as metrics as history is less relevant. std::vector> floatMetrics; std::vector> enumMetrics; - std::vector()>> intTimestamps; - std::vector()>> uint64Timestamps; - std::vector()>> floatTimestamps; - std::vector()>> stringTimestamps; - std::vector()>> enumTimestamps; + std::vector> intTimestamps; + std::vector> uint64Timestamps; + std::vector> floatTimestamps; + std::vector> stringTimestamps; + std::vector> enumTimestamps; std::vector max; std::vector min; std::vector average; diff --git a/Framework/Core/include/Framework/DriverInfo.h b/Framework/Core/include/Framework/DriverInfo.h index 41b868150e047..146602575b708 100644 --- a/Framework/Core/include/Framework/DriverInfo.h +++ b/Framework/Core/include/Framework/DriverInfo.h @@ -149,6 +149,8 @@ struct DriverInfo { std::string uniqueWorkflowId = ""; /// Metrics gathering interval unsigned short resourcesMonitoringInterval = 0; + /// Where to dump the metrics + std::string resourcesMonitoringFilename = "performanceMetrics.json"; /// Metrics gathering dump to disk interval unsigned short resourcesMonitoringDumpInterval = 0; /// Port used by the websocket control. 0 means not initialised. diff --git a/Framework/Core/include/Framework/Expressions.h b/Framework/Core/include/Framework/Expressions.h index 5a889e9ae26ec..c5f50311a7d19 100644 --- a/Framework/Core/include/Framework/Expressions.h +++ b/Framework/Core/include/Framework/Expressions.h @@ -110,6 +110,8 @@ std::string upcastTo(atype::type f); /// An expression tree node corresponding to a literal value struct LiteralNode { + using var_t = LiteralValue::stored_type; + LiteralNode() : value{-1}, type{atype::INT32} @@ -120,7 +122,12 @@ struct LiteralNode { { } - using var_t = LiteralValue::stored_type; + LiteralNode(var_t v, atype::type t) + : value{v}, + type{t} + { + } + var_t value; atype::type type = atype::NA; }; @@ -617,6 +624,12 @@ inline Node ncfg(T defaultValue, std::string path) struct Filter { Filter() = default; + Filter(std::unique_ptr&& ptr) + { + node = std::move(ptr); + (void)designateSubtrees(node.get()); + } + Filter(Node&& node_) : node{std::make_unique(std::forward(node_))} { (void)designateSubtrees(node.get()); @@ -624,7 +637,6 @@ struct Filter { Filter(Filter&& other) : node{std::forward>(other.node)} { - (void)designateSubtrees(node.get()); } Filter(std::string const& input_) : input{input_} {} @@ -700,6 +712,8 @@ std::shared_ptr createProjectorHelper(size_t nColumns, expre std::shared_ptr schema, std::vector> const& fields); +std::vector> materializeProjectors(std::vector const& projectors, std::shared_ptr const& inputSchema, std::vector> const& outputFields); + template std::shared_ptr createProjectors(framework::pack, std::vector> const& fields, gandiva::SchemaPtr schema) { diff --git a/Framework/Core/include/Framework/FairMQDeviceProxy.h b/Framework/Core/include/Framework/FairMQDeviceProxy.h index ab0d094c18486..dbdade465f09c 100644 --- a/Framework/Core/include/Framework/FairMQDeviceProxy.h +++ b/Framework/Core/include/Framework/FairMQDeviceProxy.h @@ -38,7 +38,9 @@ class FairMQDeviceProxy FairMQDeviceProxy() = default; FairMQDeviceProxy(FairMQDeviceProxy const&) = delete; void bind(std::vector const& outputs, std::vector const& inputs, - std::vector const& forwards, fair::mq::Device& device); + std::vector const& forwards, + std::function bindChannelByName, + std::function newStateRequestedCallback); /// Retrieve the transport associated to a given route. [[nodiscard]] OutputRoute const& getOutputRoute(RouteIndex routeIndex) const { return mOutputs.at(routeIndex.value); } diff --git a/Framework/Core/include/Framework/FragmentToBatch.h b/Framework/Core/include/Framework/FragmentToBatch.h new file mode 100644 index 0000000000000..3a600d71452b9 --- /dev/null +++ b/Framework/Core/include/Framework/FragmentToBatch.h @@ -0,0 +1,51 @@ +// Copyright 2019-2025 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_FRAGMENT_TO_BATCH_H_ +#define O2_FRAMEWORK_FRAGMENT_TO_BATCH_H_ + +#include +#include +#include +#include +#include + +// ============================================================================= +namespace o2::framework +{ +class FragmentToBatch +{ + public: + // The function to be used to create the required stream. + using StreamerCreator = std::function(std::shared_ptr, const std::shared_ptr& buffer)>; + + FragmentToBatch(StreamerCreator, std::shared_ptr, arrow::MemoryPool* pool = arrow::default_memory_pool()); + void setLabel(const char* label); + void fill(std::shared_ptr dataSetSchema, std::shared_ptr); + std::shared_ptr finalize(); + + std::shared_ptr streamer(std::shared_ptr buffer) + { + return mCreator(mFragment, buffer); + } + + private: + std::shared_ptr mFragment; + arrow::MemoryPool* mArrowMemoryPool = nullptr; + std::string mTableLabel; + std::shared_ptr mRecordBatch; + StreamerCreator mCreator; +}; + +// ----------------------------------------------------------------------------- +} // namespace o2::framework + +// ============================================================================= +#endif // O2_FRAMEWORK_FRAGMENT_TO_BATCH_H_ diff --git a/Framework/Core/include/Framework/GroupSlicer.h b/Framework/Core/include/Framework/GroupSlicer.h index 4cfbb8c440fd3..596e68d8cdd4c 100644 --- a/Framework/Core/include/Framework/GroupSlicer.h +++ b/Framework/Core/include/Framework/GroupSlicer.h @@ -55,7 +55,7 @@ struct GroupSlicer { { constexpr auto index = framework::has_type_at_v>(associated_pack_t{}); auto binding = o2::soa::getLabelFromTypeForKey>(mIndexColumnName); - auto bk = Entry(binding, mIndexColumnName); + auto bk = Entry(binding, o2::soa::getMatcherFromTypeForKey>(mIndexColumnName), mIndexColumnName); if constexpr (!o2::soa::is_smallgroups>) { if (table.size() == 0) { return; diff --git a/Framework/Core/include/Framework/GuiCallbackContext.h b/Framework/Core/include/Framework/GuiCallbackContext.h index 1dbb6ec30e849..5bb3148621476 100644 --- a/Framework/Core/include/Framework/GuiCallbackContext.h +++ b/Framework/Core/include/Framework/GuiCallbackContext.h @@ -23,7 +23,7 @@ namespace o2::framework { struct GuiCallbackContext; -class WSDPLHandler; +struct WSDPLHandler; struct GuiRenderer { uv_timer_t drawTimer; diff --git a/Framework/Core/include/Framework/HistogramRegistry.h b/Framework/Core/include/Framework/HistogramRegistry.h index 6db4bd0a2d0e2..49ef006f84a79 100644 --- a/Framework/Core/include/Framework/HistogramRegistry.h +++ b/Framework/Core/include/Framework/HistogramRegistry.h @@ -173,16 +173,15 @@ class HistogramRegistry template std::shared_ptr operator()(const HistName& histName); + // Apply @a callback on every single entry in the registry + void apply(std::function callback) const; // return the OutputSpec associated to the HistogramRegistry OutputSpec const spec(); - OutputRef ref(uint16_t idx, uint16_t pipelineSize); + OutputRef ref(uint16_t idx, uint16_t pipelineSize) const; void setHash(uint32_t hash); - /// returns the list of histograms, properly sorted for writing. - TList* getListOfHistograms(); - /// deletes all the histograms from the registry void clean(); @@ -220,16 +219,13 @@ class HistogramRegistry // helper function to find the histogram position in the registry template - uint32_t getHistIndex(const T& histName); + uint32_t getHistIndex(const T& histName) const; constexpr uint32_t imask(uint32_t i) const { return i & REGISTRY_BITMASK; } - // helper function to create resp. find the subList defined by path - TList* getSubList(TList* list, std::deque& path); - // helper function to split user defined path/to/hist/name string std::deque splitPath(const std::string& pathAndNameUser); @@ -431,7 +427,7 @@ std::shared_ptr HistogramRegistry::operator()(const HistName& histName) } template -uint32_t HistogramRegistry::getHistIndex(const T& histName) +uint32_t HistogramRegistry::getHistIndex(const T& histName) const { if (O2_BUILTIN_LIKELY(histName.hash == mRegistryKey[histName.idx])) { return histName.idx; diff --git a/Framework/Core/include/Framework/IndexBuilderHelpers.h b/Framework/Core/include/Framework/IndexBuilderHelpers.h index d02d5cfc59b3f..3a23d97a10d83 100644 --- a/Framework/Core/include/Framework/IndexBuilderHelpers.h +++ b/Framework/Core/include/Framework/IndexBuilderHelpers.h @@ -11,23 +11,32 @@ #ifndef O2_FRAMEWORK_INDEXBUILDERHELPERS_H_ #define O2_FRAMEWORK_INDEXBUILDERHELPERS_H_ -#include "arrow/array.h" #include #include #include -#include #include -#include + +namespace o2::soa +{ +enum struct IndexKind : int { + IdxInvalid = -1, + IdxSelf = 0, + IdxSingle = 1, + IdxSlice = 2, + IdxArray = 3 +}; +} // namespace o2::soa namespace o2::framework { -void cannotBuildAnArray(); +void cannotBuildAnArray(const char* reason); +void cannotCreateIndexBuilder(); struct ChunkedArrayIterator { ChunkedArrayIterator(std::shared_ptr source); - virtual ~ChunkedArrayIterator() = default; + void reset(std::shared_ptr& source); - std::shared_ptr mSource; + std::shared_ptr mSource = nullptr; size_t mPosition = 0; int mChunk = 0; size_t mOffset = 0; @@ -35,6 +44,7 @@ struct ChunkedArrayIterator { int const* mCurrent = nullptr; int const* mLast = nullptr; size_t mFirstIndex = 0; + size_t mSourceSize = 0; std::shared_ptr getCurrentArray(); void nextChunk(); @@ -42,114 +52,72 @@ struct ChunkedArrayIterator { int valueAt(size_t pos); }; -struct SelfIndexColumnBuilder { - SelfIndexColumnBuilder(const char* name, arrow::MemoryPool* pool); - virtual ~SelfIndexColumnBuilder() = default; - - template - inline std::shared_ptr result() const - { - std::shared_ptr array; - auto status = static_cast(mBuilder.get())->Finish(&array); - if (!status.ok()) { - cannotBuildAnArray(); - } +struct SelfBuilder { + std::unique_ptr mBuilder = nullptr; + std::unique_ptr keyIndex = nullptr; + SelfBuilder(arrow::MemoryPool* pool); + void reset(std::shared_ptr); - return std::make_shared(array); - } - std::shared_ptr field() const; - template - inline bool find(int) + inline bool find(int) const { return true; } - - template - inline void fill(int idx) - { - (void)static_cast(mBuilder.get())->Append(idx); - } - - std::string mColumnName; - std::shared_ptr mArrowType; - std::unique_ptr mBuilder = nullptr; + void fill(int idx); + std::shared_ptr result() const; }; -class IndexColumnBuilder : public SelfIndexColumnBuilder, public ChunkedArrayIterator -{ - public: - IndexColumnBuilder(std::shared_ptr source, const char* name, int listSize, arrow::MemoryPool* pool); - ~IndexColumnBuilder() override = default; +struct SingleBuilder : public ChunkedArrayIterator { + std::unique_ptr mBuilder = nullptr; + SingleBuilder(std::shared_ptr source, arrow::MemoryPool* pool); + void reset(std::shared_ptr source); - template - inline std::shared_ptr result() const - { - if constexpr (std::same_as>) { - return resultMulti(); - } else if constexpr (std::same_as) { - return resultSlice(); - } else { - return resultSingle(); - } - } + bool find(int idx); + void fill(int idx); + std::shared_ptr result() const; +}; - template - inline bool find(int idx) - { - if constexpr (std::same_as>) { - return findMulti(idx); - } else if constexpr (std::same_as) { - return findSlice(idx); - } else { - return findSingle(idx); - } - } +struct SliceBuilder : public ChunkedArrayIterator { + arrow::ArrayBuilder* mValueBuilder = nullptr; + std::unique_ptr mListBuilder = nullptr; + std::shared_ptr> mValues = nullptr; + std::shared_ptr> mCounts = nullptr; + int mValuePos = 0; + SliceBuilder(std::shared_ptr source, arrow::MemoryPool* pool); + void reset(std::shared_ptr source); - template - inline void fill(int idx) - { - ++mResultSize; - if constexpr (std::same_as>) { - fillMulti(idx); - } else if constexpr (std::same_as) { - fillSlice(idx); - } else { - fillSingle(idx); - } - } + bool find(int idx); + void fill(int idx); + std::shared_ptr result() const; - private: arrow::Status preSlice(); - arrow::Status preFind(); - - bool findSingle(int idx); - bool findSlice(int idx); - bool findMulti(int idx); - - void fillSingle(int idx); - void fillSlice(int idx); - void fillMulti(int idx); - - std::shared_ptr resultSingle() const; - std::shared_ptr resultSlice() const; - std::shared_ptr resultMulti() const; +}; - int mListSize = 1; +struct ArrayBuilder : public ChunkedArrayIterator { arrow::ArrayBuilder* mValueBuilder = nullptr; + std::vector mValues; + std::vector> mIndices; std::unique_ptr mListBuilder = nullptr; + ArrayBuilder(std::shared_ptr source, arrow::MemoryPool* pool); + void reset(std::shared_ptr source); - size_t mSourceSize = 0; - size_t mResultSize = 0; + bool find(int idx); + void fill(int idx); + std::shared_ptr result() const; - std::shared_ptr> mValuesArrow = nullptr; - std::shared_ptr> mCounts = nullptr; - std::vector mValues; - std::vector> mIndices; - int mFillOffset = 0; - int mValuePos = 0; + arrow::Status preFind(); }; -std::shared_ptr makeArrowTable(const char* label, std::vector>&& columns, std::vector>&& fields); +struct IndexColumnBuilder { + std::variant builder; + size_t mResultSize = 0; + int mColumnPos = -1; + IndexColumnBuilder(soa::IndexKind kind, int pos, arrow::MemoryPool* pool, std::shared_ptr source = nullptr); + void reset(std::shared_ptr source = nullptr); + + bool find(int idx); + void fill(int idx); + std::shared_ptr result() const; +}; } // namespace o2::framework #endif // O2_FRAMEWORK_INDEXBUILDERHELPERS_H_ diff --git a/Framework/Core/include/Framework/InitContext.h b/Framework/Core/include/Framework/InitContext.h index 8e616d276748b..7f6cec3c7a160 100644 --- a/Framework/Core/include/Framework/InitContext.h +++ b/Framework/Core/include/Framework/InitContext.h @@ -16,7 +16,7 @@ namespace o2::framework { -class ServiceRegistry; +struct ServiceRegistry; class ConfigParamRegistry; // This is a utility class to reduce the amount of boilerplate when defining diff --git a/Framework/Core/include/Framework/InputRecord.h b/Framework/Core/include/Framework/InputRecord.h index 0c9f36d00c634..c52610b6ddb62 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" @@ -34,6 +35,7 @@ #include #include #include +#include #include @@ -44,6 +46,12 @@ namespace o2::framework struct CCDBMetadataExtractor { }; +/// Tag type to retrieve the raw binary payload of a CCDB entry without ROOT +/// deserialization. The returned span is valid for the duration of the +/// processing callback. Use as: inputs.get("binding") +struct CCDBBlob { +}; + struct InputSpec; class InputSpan; class CallbackService; @@ -189,6 +197,7 @@ class InputRecord }; int getPos(const char* name) const; + int getPos(ConcreteDataMatcher matcher) const; [[nodiscard]] static InputPos getPos(std::vector const& routes, ConcreteDataMatcher matcher); [[nodiscard]] static DataRef getByPos(std::vector const& routes, InputSpan const& span, int pos, int part = 0); @@ -201,6 +210,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 { @@ -511,6 +529,39 @@ class InputRecord return cache.idToMetadata[id]; } + template + std::span get(R binding, int part = 0) const + requires std::same_as + { + auto ref = getRef(binding, part); + auto header = DataRefUtils::getHeader(ref); + if (header->payloadSerializationMethod != header::gSerializationMethodCCDB) { + throw runtime_error("Attempt to extract CCDBBlob from a non-CCDB-serialized message"); + } + return DataRefUtils::getCCDBPayloadBlob(ref); + } + + template + requires(std::same_as) + decltype(auto) get(ConcreteDataMatcher matcher, int part = 0) + { + auto pos = getPos(matcher); + if (pos < 0) { + auto msg = describeAvailableInputs(); + throw runtime_error_f("InputRecord::get: no input with binding %s found. %s", DataSpecUtils::describe(matcher).c_str(), msg.c_str()); + } + return getByPos(pos, part); + } + + template + requires(std::same_as) + decltype(auto) get(ConcreteDataMatcher matcher, int part = 0) + { + auto ref = get(matcher, part); + auto data = reinterpret_cast(ref.payload); + return std::make_unique(data, DataRefUtils::getPayloadSize(ref)); + } + /// Helper method to be used to check if a given part of the InputRecord is present. [[nodiscard]] bool isValid(std::string const& s) const { @@ -546,8 +597,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)) { @@ -656,18 +707,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 @@ -687,12 +749,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); } }; @@ -701,12 +763,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/OutputObjHeader.h b/Framework/Core/include/Framework/OutputObjHeader.h index 6e665bb697572..801642ec6af4f 100644 --- a/Framework/Core/include/Framework/OutputObjHeader.h +++ b/Framework/Core/include/Framework/OutputObjHeader.h @@ -37,6 +37,7 @@ enum OutputObjSourceType : unsigned int { /// @brief O2 header for OutputObj metadata struct OutputObjHeader : public BaseHeader { constexpr static const uint32_t sVersion = 1; + constexpr static const uint32_t MAX_REGISTRY_NAME_SIZE = 128; constexpr static const o2::header::HeaderType sHeaderType = "OutObjMD"; constexpr static const o2::header::SerializationMethod sSerializationMethod = o2::header::gSerializationMethodNone; OutputObjHandlingPolicy mPolicy; @@ -44,6 +45,10 @@ struct OutputObjHeader : public BaseHeader { uint32_t mTaskHash; uint16_t mPipelineIndex = 0; uint16_t mPipelineSize = 1; + // Name of the actual container for the object, e.g. the HistogramRegistry name + char containerName[MAX_REGISTRY_NAME_SIZE] = {0}; + // Wether or not the container should have a name + char createContainer = false; constexpr OutputObjHeader() : BaseHeader(sizeof(OutputObjHeader), sHeaderType, sSerializationMethod, sVersion), 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/ResourcePolicyHelpers.h b/Framework/Core/include/Framework/ResourcePolicyHelpers.h index abee264d75104..17599f9afb1a7 100644 --- a/Framework/Core/include/Framework/ResourcePolicyHelpers.h +++ b/Framework/Core/include/Framework/ResourcePolicyHelpers.h @@ -22,6 +22,7 @@ namespace o2::framework struct ResourcePolicyHelpers { static ResourcePolicy trivialTask(char const* taskMatcher); static ResourcePolicy cpuBoundTask(char const* taskMatcher, int maxCPUs = 1); + static ResourcePolicy rateLimitedSharedMemoryBoundTask(char const* taskMatcher, int maxMemory, int maxTimeslices); static ResourcePolicy sharedMemoryBoundTask(char const* taskMatcher, int maxMemory); }; diff --git a/Framework/Core/include/Framework/RootArrowFilesystem.h b/Framework/Core/include/Framework/RootArrowFilesystem.h index 5aceaed077001..07aaa348c220a 100644 --- a/Framework/Core/include/Framework/RootArrowFilesystem.h +++ b/Framework/Core/include/Framework/RootArrowFilesystem.h @@ -146,7 +146,7 @@ class TFileFileSystem : public VirtualRootFileSystemBase public: arrow::Result GetFileInfo(const std::string& path) override; - TFileFileSystem(TDirectoryFile* f, size_t readahead, RootObjectReadingFactory&); + TFileFileSystem(TDirectoryFile* f, size_t readahead, RootObjectReadingFactory&, bool ownsFile = true); ~TFileFileSystem() override; @@ -172,6 +172,7 @@ class TFileFileSystem : public VirtualRootFileSystemBase private: TDirectoryFile* mFile; RootObjectReadingFactory& mObjectFactory; + bool mOwnsFile = true; }; class TBufferFileFS : public VirtualRootFileSystemBase diff --git a/Framework/Core/include/Framework/SerializationMethods.h b/Framework/Core/include/Framework/SerializationMethods.h index 31b9d24013ab4..68c509d36905f 100644 --- a/Framework/Core/include/Framework/SerializationMethods.h +++ b/Framework/Core/include/Framework/SerializationMethods.h @@ -15,6 +15,7 @@ /// @brief Type wrappers for enfording a specific serialization method #include "Framework/TypeTraits.h" +#include namespace o2::framework { @@ -43,6 +44,9 @@ namespace o2::framework /// - or - /// ROOTSerialized(object, "classname")); template + requires(!std::is_pointer_v && (std::same_as || + std::same_as || + std::is_void_v)) class ROOTSerialized { public: @@ -50,9 +54,6 @@ class ROOTSerialized using wrapped_type = T; using hint_type = HintType; - static_assert(std::is_pointer::value == false, "wrapped type can not be a pointer"); - static_assert(std::is_pointer::value == false, "hint type can not be a pointer"); - ROOTSerialized() = delete; ROOTSerialized(wrapped_type& ref, hint_type* hint = nullptr) : mRef(ref), mHint(hint) {} @@ -67,6 +68,9 @@ class ROOTSerialized }; template + requires(!std::is_pointer_v && (std::same_as || + std::same_as || + std::is_void_v)) class CCDBSerialized { public: @@ -74,9 +78,6 @@ class CCDBSerialized using wrapped_type = T; using hint_type = HintType; - static_assert(std::is_pointer::value == false, "wrapped type can not be a pointer"); - static_assert(std::is_pointer::value == false, "hint type can not be a pointer"); - CCDBSerialized() = delete; CCDBSerialized(wrapped_type& ref, hint_type* hint = nullptr) : mRef(ref), mHint(hint) {} diff --git a/Framework/Core/include/Framework/ServiceRegistry.h b/Framework/Core/include/Framework/ServiceRegistry.h index ebafd466929ff..d6516e31be62d 100644 --- a/Framework/Core/include/Framework/ServiceRegistry.h +++ b/Framework/Core/include/Framework/ServiceRegistry.h @@ -158,7 +158,7 @@ struct ServiceRegistry { /// not bonded to a specific stream, e.g. the /// name of the data processor, its inputs and outputs, /// it's algorithm. - static Salt dataProcessorSalt(short dataProcessorId) + static Salt dataProcessorSalt(short /* dataProcessorId */) { // FIXME: old behaviour for now // return {0, dataProcessorId}; diff --git a/Framework/Core/include/Framework/ServiceRegistryRef.h b/Framework/Core/include/Framework/ServiceRegistryRef.h index 910d4e726c080..85aad6d70e93b 100644 --- a/Framework/Core/include/Framework/ServiceRegistryRef.h +++ b/Framework/Core/include/Framework/ServiceRegistryRef.h @@ -112,6 +112,8 @@ class ServiceRegistryRef mRegistry.unlock(mSalt); } + static ServiceRegistryRef *globalDeviceRef(ServiceRegistryRef *ref = nullptr); + private: ServiceRegistry& mRegistry; ServiceRegistry::Salt mSalt; diff --git a/Framework/Core/include/Framework/ServiceSpec.h b/Framework/Core/include/Framework/ServiceSpec.h index 5684889e85376..aa762b5d039e0 100644 --- a/Framework/Core/include/Framework/ServiceSpec.h +++ b/Framework/Core/include/Framework/ServiceSpec.h @@ -26,12 +26,12 @@ struct ProgOptions; namespace o2::framework { -struct InitContext; +class InitContext; struct DeviceSpec; struct ServiceRegistry; -struct ServiceRegistryRef; +class ServiceRegistryRef; struct DeviceState; -struct ProcessingContext; +class ProcessingContext; class EndOfStreamContext; struct ConfigContext; struct WorkflowSpecNode; diff --git a/Framework/Core/include/Framework/StringHelpers.h b/Framework/Core/include/Framework/StringHelpers.h index 8a2d892062f70..a2ee758435efc 100644 --- a/Framework/Core/include/Framework/StringHelpers.h +++ b/Framework/Core/include/Framework/StringHelpers.h @@ -171,7 +171,7 @@ constexpr auto get_str(const char (&str)[N]) } template -constexpr auto get_size(const char (&str)[N]) +constexpr auto get_size(const char (&)[N]) { return N; } diff --git a/Framework/Core/include/Framework/TableBuilder.h b/Framework/Core/include/Framework/TableBuilder.h index 1eb493bfd052d..845820dfe4bff 100644 --- a/Framework/Core/include/Framework/TableBuilder.h +++ b/Framework/Core/include/Framework/TableBuilder.h @@ -15,10 +15,8 @@ #include "Framework/ASoA.h" #include "Framework/StructToTuple.h" #include "Framework/RuntimeError.h" -#include "arrow/type_traits.h" // Apparently needs to be on top of the arrow includes. -#include #include #include @@ -27,6 +25,7 @@ #include #include #include +#include #include #include @@ -765,88 +764,5 @@ class TableBuilder std::shared_ptr mSchema; std::vector> mArrays; }; - -template -auto makeEmptyTable(const char* name) -{ - TableBuilder b; - [[maybe_unused]] auto writer = b.cursor(); - b.setLabel(name); - return b.finalize(); -} - -template -auto makeEmptyTable() -{ - TableBuilder b; - [[maybe_unused]] auto writer = b.cursor(typename aod::MetadataTrait>::metadata::persistent_columns_t{}); - b.setLabel(aod::label()); - return b.finalize(); -} - -template -auto makeEmptyTable(const char* name, framework::pack p) -{ - TableBuilder b; - [[maybe_unused]] auto writer = b.cursor(p); - b.setLabel(name); - return b.finalize(); -} - -std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, size_t nColumns, - expressions::Projector* projectors, const char* name, std::shared_ptr& projector); - -/// Expression-based column generator to materialize columns -template - requires(soa::has_configurable_extension::metadata>) -auto spawner(std::shared_ptr const& fullTable, const char* name, o2::framework::expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) -{ - using placeholders_pack_t = typename o2::aod::MetadataTrait::metadata::placeholders_pack_t; - if (fullTable->num_rows() == 0) { - return makeEmptyTable(name, placeholders_pack_t{}); - } - return spawnerHelper(fullTable, schema, framework::pack_size(placeholders_pack_t{}), projectors, name, projector); -} - -template - requires(soa::has_configurable_extension::metadata>) -auto spawner(std::vector>&& tables, const char* name, o2::framework::expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) -{ - auto fullTable = soa::ArrowHelpers::joinTables(std::move(tables), std::span{o2::aod::MetadataTrait::metadata::base_table_t::originalLabels}); - return spawner(fullTable, name, projectors, projector, schema); -} - -template - requires(soa::has_extension::metadata> && !soa::has_configurable_extension::metadata>) -auto spawner(std::shared_ptr const& fullTable, const char* name, expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) -{ - using expression_pack_t = typename o2::aod::MetadataTrait::metadata::expression_pack_t; - if (fullTable->num_rows() == 0) { - return makeEmptyTable(name, expression_pack_t{}); - } - return spawnerHelper(fullTable, schema, framework::pack_size(expression_pack_t{}), projectors, name, projector); -} - -template - requires(soa::has_extension::metadata> && !soa::has_configurable_extension::metadata>) -auto spawner(std::vector>&& tables, const char* name, expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) -{ - auto fullTable = soa::ArrowHelpers::joinTables(std::move(tables), std::span{o2::aod::MetadataTrait::metadata::base_table_t::originalLabels}); - return spawner(fullTable, name, projectors, projector, schema); -} - -template -auto spawner(framework::pack, std::vector>&& tables, const char* name, expressions::Projector* projectors, std::shared_ptr& projector, std::shared_ptr const& schema) -{ - std::array labels{"original"}; - auto fullTable = soa::ArrowHelpers::joinTables(std::move(tables), std::span{labels}); - if (fullTable->num_rows() == 0) { - return makeEmptyTable(name, framework::pack{}); - } - return spawnerHelper(fullTable, schema, sizeof...(C), projectors, name, projector); -} - -template -using iterator_tuple_t = std::tuple; } // namespace o2::framework #endif // FRAMEWORK_TABLEBUILDER_H 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/include/Framework/Variant.h b/Framework/Core/include/Framework/Variant.h index e69ca05b91d98..7121c5fad0669 100644 --- a/Framework/Core/include/Framework/Variant.h +++ b/Framework/Core/include/Framework/Variant.h @@ -274,13 +274,24 @@ struct variant_helper> { // Allocates a new store and copies into it. static void set(void* store, std::vector value) { - new (reinterpret_cast*>(store)) std::vector{}; - *(reinterpret_cast*>(store)) = value; + auto ptr = reinterpret_cast*>(store); + new (ptr) std::vector{value}; } static std::vector const& get(const void* store) { return *(reinterpret_cast const*>(store)); } }; +template <> +struct variant_helper { + static void set(void* store, std::string* values, size_t size) + { + auto ptr = reinterpret_cast*>(store); + new (ptr) std::vector{values, values + size}; + } + + static std::string const* get(const void* store) { return (*(reinterpret_cast const*>(store))).data(); } +}; + template <> struct variant_helper { static const char* get(const void* store) { return *reinterpret_cast(store); } @@ -360,6 +371,16 @@ class Variant return variant_helper::get(&mStore); } + template + [[nodiscard]] std::string const* get() const + requires(std::same_as) + { + if (mType != VariantType::ArrayString) { + throw runtime_error_f("Variant::get: Mismatch between types %d %d.", mType, VariantType::ArrayString); + } + return variant_helper::get(&mStore); + } + template void set(T value) { diff --git a/Framework/Core/include/Framework/VariantPropertyTreeHelpers.h b/Framework/Core/include/Framework/VariantPropertyTreeHelpers.h index a51e3e03ffc5e..05ab71d39b0e4 100644 --- a/Framework/Core/include/Framework/VariantPropertyTreeHelpers.h +++ b/Framework/Core/include/Framework/VariantPropertyTreeHelpers.h @@ -36,7 +36,7 @@ boost::property_tree::ptree basicVectorToBranch(std::vector&& values) } template -boost::property_tree::ptree vectorToBranch(T* values, size_t size) +boost::property_tree::ptree vectorToBranch(T const* values, size_t size) { boost::property_tree::ptree branch; branch.put_child("values", basicVectorToBranch(values, size)); @@ -150,17 +150,17 @@ extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(f extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(int*, size_t); extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(double*, size_t); extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(bool*, size_t); -extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(std::basic_string*, size_t); +extern template boost::property_tree::ptree o2::framework::basicVectorToBranch(std::basic_string const*, size_t); extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); -extern template boost::property_tree::ptree o2::framework::vectorToBranch(float*, size_t); -extern template boost::property_tree::ptree o2::framework::vectorToBranch(int*, size_t); -extern template boost::property_tree::ptree o2::framework::vectorToBranch(double*, size_t); -extern template boost::property_tree::ptree o2::framework::vectorToBranch(bool*, size_t); -extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::basic_string*, size_t); +extern template boost::property_tree::ptree o2::framework::vectorToBranch(float const*, size_t); +extern template boost::property_tree::ptree o2::framework::vectorToBranch(int const*, size_t); +extern template boost::property_tree::ptree o2::framework::vectorToBranch(double const*, size_t); +extern template boost::property_tree::ptree o2::framework::vectorToBranch(bool const*, size_t); +extern template boost::property_tree::ptree o2::framework::vectorToBranch(std::basic_string const*, size_t); extern template boost::property_tree::ptree o2::framework::labeledArrayToBranch(o2::framework::LabeledArray&& array); extern template boost::property_tree::ptree o2::framework::labeledArrayToBranch(o2::framework::LabeledArray&& array); 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/hyperloop-perf-server/perf_mcp_server.py b/Framework/Core/scripts/hyperloop-perf-server/perf_mcp_server.py new file mode 100644 index 0000000000000..cce2d31bf00e3 --- /dev/null +++ b/Framework/Core/scripts/hyperloop-perf-server/perf_mcp_server.py @@ -0,0 +1,425 @@ +#!/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. +"""Hyperloop perf profile MCP server. + +Fetches Linux ``perf script`` output files hosted on the Hyperloop web server, +parses and indexes them in memory, and exposes query tools so an AI assistant +can investigate performance hotspots without ever seeing the raw file. + +Usage +----- + python3 perf_mcp_server.py + +Multiple profiles can be loaded simultaneously, enabling before/after +comparisons via the ``compare`` tool. +""" + +from __future__ import annotations + +import asyncio +import os +import re +from collections import defaultdict +from dataclasses import dataclass, field +from typing import Optional + +import httpx +from mcp.server.fastmcp import FastMCP + +# --------------------------------------------------------------------------- +# Perf profile data model +# --------------------------------------------------------------------------- + +_OFFSET_RE = re.compile(r"\+0x[0-9a-fA-F]+") + + +def _strip(sym: str) -> str: + """Remove hex offsets from a symbol name.""" + return _OFFSET_RE.sub("", sym) + + +@dataclass +class PerfProfile: + url: str + total_cycles: int + processes: set[str] + # leaf[sym] = cycles where sym was the innermost (hot) frame + leaf: dict[str, int] + # inclusive[sym] = cycles where sym appeared anywhere in the stack + inclusive: dict[str, int] + # callers[sym][caller] = cycles attributed to this caller→sym edge + callers: dict[str, dict[str, int]] + # callees[sym][callee] = cycles attributed to this sym→callee edge + callees: dict[str, dict[str, int]] + # raw stacks for chain queries: (cycles, process_name, [sym0, sym1, ...]) + # sym0 is the innermost (leaf) frame; sym[-1] is the outermost caller. + stacks: list[tuple[int, str, list[str]]] = field(default_factory=list) + + +def _parse(text: str) -> PerfProfile: + """Parse ``perf script`` text output into a PerfProfile.""" + leaf: dict[str, int] = defaultdict(int) + inclusive: dict[str, int] = defaultdict(int) + callers: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) + callees: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) + stacks: list[tuple[int, str, list[str]]] = [] + processes: set[str] = set() + total_cycles = 0 + + cycles = 0 + process = "" + stack: list[str] = [] + + def _flush() -> None: + nonlocal total_cycles + if not stack or not cycles: + return + stacks.append((cycles, process, list(stack))) + total_cycles += cycles + leaf[stack[0]] += cycles + seen: set[str] = set() + for i, sym in enumerate(stack): + if sym not in seen: + inclusive[sym] += cycles + seen.add(sym) + # stack[i] is called by stack[i+1]; stack[i] calls stack[i-1] + if i > 0: + callers[stack[i - 1]][stack[i]] += cycles + callees[stack[i]][stack[i - 1]] += cycles + + for line in text.splitlines(): + if not line: + continue + if not line[0].isspace(): + _flush() + m = re.search(r"(\d+) cycles:", line) + cycles = int(m.group(1)) if m else 0 + parts = line.split() + process = parts[0] if parts else "" + processes.add(process) + stack = [] + elif line[0] == "\t": + parts = line.split() + if len(parts) >= 2: + stack.append(_strip(parts[1])) + + _flush() + + return PerfProfile( + url="", + total_cycles=total_cycles, + processes=processes, + leaf=dict(leaf), + inclusive=dict(inclusive), + callers={k: dict(v) for k, v in callers.items()}, + callees={k: dict(v) for k, v in callees.items()}, + stacks=stacks, + ) + + +# --------------------------------------------------------------------------- +# Profile registry +# --------------------------------------------------------------------------- + +_profiles: dict[str, PerfProfile] = {} + + +def _get(name: str) -> PerfProfile: + p = _profiles.get(name) + if p is None: + available = ", ".join(_profiles.keys()) if _profiles else "(none)" + raise ValueError( + f"No profile named '{name}'. Loaded profiles: {available}. " + f"Use load_profile first." + ) + return p + + +def _pct(cycles: int, total: int) -> str: + return f"{cycles * 100 / total:.2f}%" if total else "N/A" + + +def _top_table(hot: dict[str, int], total: int, n: int, process_note: str = "") -> str: + rows = sorted(hot.items(), key=lambda x: -x[1])[:n] + if not rows: + return "No data." + header = f"{'cycles':>16} {'%':>7} symbol" + if process_note: + header = f"[filtered: {process_note}]\n" + header + lines = [header] + for sym, cyc in rows: + lines.append(f"{cyc:>16,} {_pct(cyc, total):>7} {sym}") + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# MCP server +# --------------------------------------------------------------------------- + +mcp = FastMCP("Hyperloop Perf") + + +@mcp.tool() +async def load_profile(url: str, name: str = "", token: str = "", proxy_token: str = "") -> str: + """Fetch a ``perf script`` text file and index it for querying. + + The file is downloaded once and kept in memory. Subsequent tool calls + use the in-memory index and do not re-fetch. + + Args: + url: Direct URL to the perf script text file. + name: Human-friendly label (defaults to the filename portion of the URL). + token: Hyperloop auth token. Falls back to HYPERLOOP_TOKEN env var. + proxy_token: Bearer token for the local proxy. Falls back to PROXY_TOKEN env var, + then to token. + """ + token = token or os.environ.get("HYPERLOOP_TOKEN", "") + proxy_token = proxy_token or os.environ.get("PROXY_TOKEN", "") or token + + # Rewrite alimonitor.cern.ch URLs through the local proxy (same pattern as + # connect_hyperloop). The proxy must have a route like: + # {"prefix": "/alimonitor/", "upstream": "https://alimonitor.cern.ch", "token": "..."} + fetch_url = url + if "alimonitor.cern.ch" in url: + path = url.split("alimonitor.cern.ch", 1)[1].lstrip("/") + fetch_url = f"http://localhost:8888/alimonitor/{path}" + + headers = {"Authorization": f"Bearer {proxy_token}"} if proxy_token else {} + headers["Accept-Encoding"] = "identity" + + async with httpx.AsyncClient(verify=False) as client: + for attempt in range(3): + try: + r = await client.get(fetch_url, headers=headers, timeout=300.0, follow_redirects=True) + r.raise_for_status() + break + except (httpx.RemoteProtocolError, httpx.ReadError) as exc: + if attempt == 2: + raise + text = r.content.decode("utf-8", errors="replace") + + profile = await asyncio.get_event_loop().run_in_executor(None, _parse, text) + profile.url = url + + pname = name or url.rstrip("/").split("/")[-1] + _profiles[pname] = profile + + return ( + f"Loaded '{pname}': {len(profile.stacks):,} samples, " + f"{profile.total_cycles:,} total cycles, " + f"processes: {', '.join(sorted(profile.processes))}" + ) + + +@mcp.tool() +def list_profiles() -> str: + """List all loaded perf profiles with their basic stats.""" + if not _profiles: + return "No profiles loaded. Use load_profile first." + lines = [] + for name, p in _profiles.items(): + lines.append( + f"{name}: {len(p.stacks):,} samples, {p.total_cycles:,} cycles, " + f"url={p.url}" + ) + return "\n".join(lines) + + +@mcp.tool() +def drop_profile(name: str) -> str: + """Free a loaded profile from memory. + + Args: + name: Profile name as returned by load_profile. + """ + _get(name) + del _profiles[name] + return f"Dropped profile '{name}'." + + +@mcp.tool() +def top_functions(profile_name: str, n: int = 40, process: str = "") -> str: + """Show the hottest leaf functions (where CPU was actually executing). + + Args: + profile_name: Profile name as returned by load_profile. + n: Number of entries to return (default 40). + process: Optional process name filter (exact match). + """ + p = _get(profile_name) + if process: + hot: dict[str, int] = defaultdict(int) + for cyc, proc, stack in p.stacks: + if proc == process and stack: + hot[stack[0]] += cyc + total = sum(hot.values()) + return _top_table(dict(hot), total, n, process) + return _top_table(p.leaf, p.total_cycles, n) + + +@mcp.tool() +def top_inclusive(profile_name: str, n: int = 40, process: str = "") -> str: + """Show the hottest functions by inclusive cycles (appears anywhere in stack). + + Args: + profile_name: Profile name as returned by load_profile. + n: Number of entries to return (default 40). + process: Optional process name filter (exact match). + """ + p = _get(profile_name) + if process: + hot: dict[str, int] = defaultdict(int) + for cyc, proc, stack in p.stacks: + if proc != process: + continue + for sym in set(stack): + hot[sym] += cyc + total = sum(cyc for cyc, proc, _ in p.stacks if proc == process) + return _top_table(dict(hot), total, n, process) + return _top_table(p.inclusive, p.total_cycles, n) + + +@mcp.tool() +def callers_of(profile_name: str, sym: str, n: int = 20) -> str: + """Show what calls a given function, weighted by cycles. + + Uses substring matching on the symbol name. + + Args: + profile_name: Profile name as returned by load_profile. + sym: Symbol name (or substring) to look up. + n: Number of callers to return (default 20). + """ + p = _get(profile_name) + merged: dict[str, int] = defaultdict(int) + for fn, caller_map in p.callers.items(): + if sym in fn: + for caller, cyc in caller_map.items(): + merged[caller] += cyc + if not merged: + return f"No callers found for '{sym}'." + return _top_table(dict(merged), p.total_cycles, n) + + +@mcp.tool() +def callees_of(profile_name: str, sym: str, n: int = 20) -> str: + """Show what a given function calls, weighted by cycles. + + Uses substring matching on the symbol name. + + Args: + profile_name: Profile name as returned by load_profile. + sym: Symbol name (or substring) to look up. + n: Number of callees to return (default 20). + """ + p = _get(profile_name) + merged: dict[str, int] = defaultdict(int) + for fn, callee_map in p.callees.items(): + if sym in fn: + for callee, cyc in callee_map.items(): + merged[callee] += cyc + if not merged: + return f"No callees found for '{sym}'." + return _top_table(dict(merged), p.total_cycles, n) + + +@mcp.tool() +def chains_through(profile_name: str, sym: str, depth: int = 3, n: int = 20, process: str = "") -> str: + """Show call chain windows centered on a function. + + Finds all samples where ``sym`` appears in the stack and returns the + most common surrounding call chains, each showing ``depth`` frames on + either side. + + Args: + profile_name: Profile name as returned by load_profile. + sym: Symbol name (or substring) to search for. + depth: Number of frames to show on each side (default 3). + n: Number of top chains to return (default 20). + process: Optional process name filter (exact match). + """ + p = _get(profile_name) + chains: dict[str, int] = defaultdict(int) + + for cyc, proc, stack in p.stacks: + if process and proc != process: + continue + for i, frame in enumerate(stack): + if sym in frame: + start = max(0, i - depth) + end = min(len(stack), i + depth + 1) + chain = " <- ".join(stack[start:end]) + chains[chain] += cyc + break + + if not chains: + return f"No samples found containing '{sym}'." + + lines = [f"Call chains through '{sym}' (depth={depth}):"] + lines.append(f"{'cycles':>16} {'%':>7} chain") + for chain, cyc in sorted(chains.items(), key=lambda x: -x[1])[:n]: + lines.append(f"{cyc:>16,} {_pct(cyc, p.total_cycles):>7} {chain}") + return "\n".join(lines) + + +@mcp.tool() +def compare(name_a: str, name_b: str, n: int = 40, mode: str = "leaf") -> str: + """Compare two profiles and show which functions changed the most. + + Normalises each profile's cycles to a fraction of its total so that + differences in overall run length do not skew the comparison. + Positive Δ means the function got *heavier* in B relative to A. + + Args: + name_a: Baseline profile name. + name_b: Comparison profile name. + n: Number of entries to show (default 40). + mode: "leaf" (default) or "inclusive". + """ + a = _get(name_a) + b = _get(name_b) + + hot_a = a.leaf if mode == "leaf" else a.inclusive + hot_b = b.leaf if mode == "leaf" else b.inclusive + total_a = a.total_cycles or 1 + total_b = b.total_cycles or 1 + + all_syms = set(hot_a) | set(hot_b) + diffs = [] + for sym in all_syms: + fa = hot_a.get(sym, 0) / total_a + fb = hot_b.get(sym, 0) / total_b + diffs.append((fb - fa, sym, fa, fb)) + + diffs.sort(key=lambda x: -abs(x[0])) + + lines = [ + f"Comparing '{name_a}' (A) vs '{name_b}' (B) [{mode}]", + f"A total: {a.total_cycles:,} cycles B total: {b.total_cycles:,} cycles", + f"", + f"{'Δ%':>8} {'A%':>7} {'B%':>7} symbol", + ] + for delta, sym, fa, fb in diffs[:n]: + lines.append(f"{delta*100:>+8.2f} {fa*100:>7.2f} {fb*100:>7.2f} {sym}") + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + +def main() -> None: + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/Framework/Core/scripts/hyperloop-perf-server/pyproject.toml b/Framework/Core/scripts/hyperloop-perf-server/pyproject.toml new file mode 100644 index 0000000000000..33224df62e694 --- /dev/null +++ b/Framework/Core/scripts/hyperloop-perf-server/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "hyperloop-perf-server" +version = "0.1.0" +description = "MCP server for querying Linux perf profiles from Hyperloop workflows" +requires-python = ">=3.11" +dependencies = [ + "mcp>=1.0.0", + "httpx>=0.27", +] + +[project.scripts] +hyperloop-perf-server = "perf_mcp_server:main" + +[tool.hatch.build.targets.wheel] +include = ["perf_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/AODReaderHelpers.cxx b/Framework/Core/src/AODReaderHelpers.cxx deleted file mode 100644 index 2587b8e4ca03a..0000000000000 --- a/Framework/Core/src/AODReaderHelpers.cxx +++ /dev/null @@ -1,211 +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 "Framework/AODReaderHelpers.h" -#include "Framework/AnalysisHelpers.h" -#include "Framework/AnalysisDataModelHelpers.h" -#include "Framework/DataProcessingHelpers.h" -#include "Framework/ExpressionHelpers.h" -#include "Framework/AlgorithmSpec.h" -#include "Framework/ControlService.h" -#include "Framework/CallbackService.h" -#include "Framework/EndOfStreamContext.h" -#include "Framework/DataSpecUtils.h" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace o2::framework::readers -{ -auto setEOSCallback(InitContext& ic) -{ - ic.services().get().set( - [](EndOfStreamContext& eosc) { - auto& control = eosc.services().get(); - control.endOfStream(); - control.readyToQuit(QuitRequest::Me); - }); -} - -template -static inline auto doExtractOriginal(framework::pack, ProcessingContext& pc) -{ - if constexpr (sizeof...(Ts) == 1) { - return pc.inputs().get(aod::MetadataTrait>>::metadata::tableLabel())->asArrowTable(); - } else { - return std::vector{pc.inputs().get(aod::MetadataTrait::metadata::tableLabel())->asArrowTable()...}; - } -} - -template -static inline auto extractOriginalsTuple(framework::pack, ProcessingContext& pc) -{ - return std::make_tuple(extractTypedOriginal(pc)...); -} - -template -static inline auto extractOriginalsVector(framework::pack, ProcessingContext& pc) -{ - return std::vector{extractOriginal(pc)...}; -} - -template refs> -static inline auto extractOriginals(ProcessingContext& pc) -{ - return [&](std::index_sequence) -> std::vector> { - return {pc.inputs().get(o2::aod::label())->asArrowTable()...}; - }(std::make_index_sequence()); -} -namespace -{ -template - requires(D::exclusive) -auto make_build(D metadata, InputSpec const& input, ProcessingContext& pc) -{ - using metadata_t = decltype(metadata); - using Key = typename metadata_t::Key; - using index_pack_t = typename metadata_t::index_pack_t; - constexpr auto sources = metadata_t::sources; - return o2::framework::IndexBuilder::indexBuilder(input.binding.c_str(), - extractOriginals(pc), - index_pack_t{}); -} - -template - requires(!D::exclusive) -auto make_build(D metadata, InputSpec const& input, ProcessingContext& pc) -{ - using metadata_t = decltype(metadata); - using Key = typename metadata_t::Key; - using index_pack_t = typename metadata_t::index_pack_t; - constexpr auto sources = metadata_t::sources; - return o2::framework::IndexBuilder::indexBuilder(input.binding.c_str(), - extractOriginals(pc), - index_pack_t{}); -} -} // namespace - -AlgorithmSpec AODReaderHelpers::indexBuilderCallback(std::vector& requested) -{ - return AlgorithmSpec::InitCallback{[requested](InitContext& /*ic*/) { - return [requested](ProcessingContext& pc) { - auto outputs = pc.outputs(); - // spawn tables - for (auto& input : requested) { - auto&& [origin, description, version] = DataSpecUtils::asConcreteDataMatcher(input); - if (description == header::DataDescription{"MA_RN2_EX"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run2MatchedExclusiveMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN2_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run2MatchedSparseMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN3_EX"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run3MatchedExclusiveMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN3_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run3MatchedSparseMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_BCCOL_EX"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::MatchedBCCollisionsExclusiveMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_BCCOL_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::MatchedBCCollisionsSparseMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_BCCOLS_EX"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::MatchedBCCollisionsExclusiveMultiMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_BCCOLS_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::MatchedBCCollisionsSparseMultiMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN3_BC_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run3MatchedToBCSparseMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN3_BC_EX"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run3MatchedToBCExclusiveMetadata{}, input, pc)); - } else if (description == header::DataDescription{"MA_RN2_BC_SP"}) { - outputs.adopt(Output{origin, description, version}, make_build(o2::aod::Run2MatchedToBCSparseMetadata{}, input, pc)); - } else { - throw std::runtime_error("Not an index table"); - } - } - }; - }}; -} - -namespace -{ -template -auto make_spawn(InputSpec const& input, ProcessingContext& pc) -{ - using metadata_t = o2::aod::MetadataTrait::metadata; - constexpr auto sources = metadata_t::sources; - static std::shared_ptr projector = nullptr; - static std::shared_ptr schema = std::make_shared(o2::soa::createFieldsFromColumns(typename metadata_t::expression_pack_t{})); - static auto projectors = [](framework::pack) -> std::array - { - return {{std::move(C::Projector())...}}; - } - (typename metadata_t::expression_pack_t{}); - return o2::framework::spawner(extractOriginals(pc), input.binding.c_str(), projectors.data(), projector, schema); -} -} // namespace - -AlgorithmSpec AODReaderHelpers::aodSpawnerCallback(std::vector& requested) -{ - return AlgorithmSpec::InitCallback{[requested](InitContext& /*ic*/) { - return [requested](ProcessingContext& pc) { - auto outputs = pc.outputs(); - // spawn tables - for (auto& input : requested) { - auto&& [origin, description, version] = DataSpecUtils::asConcreteDataMatcher(input); - if (description == header::DataDescription{"EXTRACK"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXTRACK_IU"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXTRACKCOV"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXTRACKCOV_IU"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXTRACKEXTRA"}) { - if (version == 0U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (version == 1U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (version == 2U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } - } else if (description == header::DataDescription{"EXMFTTRACK"}) { - if (version == 0U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (version == 1U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } - } else if (description == header::DataDescription{"EXMFTTRACKCOV"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXFWDTRACK"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXFWDTRACKCOV"}) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (description == header::DataDescription{"EXMCPARTICLE"}) { - if (version == 0U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } else if (version == 1U) { - outputs.adopt(Output{origin, description, version}, make_spawn>(input, pc)); - } - } else { - throw runtime_error("Not an extended table"); - } - } - }; - }}; -} - -} // namespace o2::framework::readers diff --git a/Framework/Core/src/ASoA.cxx b/Framework/Core/src/ASoA.cxx index 83ca358525f9f..1c73b257f81e4 100644 --- a/Framework/Core/src/ASoA.cxx +++ b/Framework/Core/src/ASoA.cxx @@ -62,41 +62,71 @@ SelectionVector sliceSelection(std::span const& mSelectedRows, in auto start_iterator = std::lower_bound(mSelectedRows.begin(), mSelectedRows.end(), start); auto stop_iterator = std::lower_bound(start_iterator, mSelectedRows.end(), end); SelectionVector slicedSelection{start_iterator, stop_iterator}; - std::transform(slicedSelection.begin(), slicedSelection.end(), slicedSelection.begin(), - [&start](int64_t idx) { - return idx - static_cast(start); - }); + std::ranges::transform(slicedSelection.begin(), slicedSelection.end(), slicedSelection.begin(), + [&start](int64_t idx) { + return idx - static_cast(start); + }); return slicedSelection; } -std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables) +{ + std::vector> fields; + std::vector> columns; + bool notEmpty = (tables[0]->num_rows() != 0); + std::ranges::for_each(tables, [&fields, &columns, notEmpty](auto const& t) { + std::ranges::copy(t->fields(), std::back_inserter(fields)); + if (notEmpty) { + std::ranges::copy(t->columns(), std::back_inserter(columns)); + } + }); + auto schema = std::make_shared(fields); + return arrow::Table::Make(schema, columns); +} + +namespace +{ +template + requires(std::same_as) +auto makeString(T const& str) +{ + return str.c_str(); +} +template + requires(std::same_as) +auto makeString(T const& str) +{ + return str; +} + +template +void canNotJoin(std::vector> const& tables, std::span labels) { - if (tables.size() == 1) { - return tables[0]; - } for (auto i = 0U; i < tables.size() - 1; ++i) { if (tables[i]->num_rows() != tables[i + 1]->num_rows()) { throw o2::framework::runtime_error_f("Tables %s and %s have different sizes (%d vs %d) and cannot be joined!", - labels[i], labels[i + 1], tables[i]->num_rows(), tables[i + 1]->num_rows()); + makeString(labels[i]), makeString(labels[i + 1]), tables[i]->num_rows(), tables[i + 1]->num_rows()); } } - std::vector> fields; - std::vector> columns; +} +} // namespace - for (auto& t : tables) { - auto tf = t->fields(); - std::copy(tf.begin(), tf.end(), std::back_inserter(fields)); +std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +{ + if (tables.size() == 1) { + return tables[0]; } + canNotJoin(tables, labels); + return joinTables(std::forward>>(tables)); +} - auto schema = std::make_shared(fields); - - if (tables[0]->num_rows() != 0) { - for (auto& t : tables) { - auto tc = t->columns(); - std::copy(tc.begin(), tc.end(), std::back_inserter(columns)); - } +std::shared_ptr ArrowHelpers::joinTables(std::vector>&& tables, std::span labels) +{ + if (tables.size() == 1) { + return tables[0]; } - return arrow::Table::Make(schema, columns); + canNotJoin(tables, labels); + return joinTables(std::forward>>(tables)); } std::shared_ptr ArrowHelpers::concatTables(std::vector>&& tables) @@ -105,7 +135,6 @@ std::shared_ptr ArrowHelpers::concatTables(std::vector> columns; - assert(tables.size() > 1); std::vector> resultFields = tables[0]->schema()->fields(); auto compareFields = [](std::shared_ptr const& f1, std::shared_ptr const& f2) { // Let's do this with stable sorting. @@ -135,13 +164,12 @@ std::shared_ptr ArrowHelpers::concatTables(std::vector(chunks)); } - auto result = arrow::Table::Make(std::make_shared(resultFields), columns); - return result; + return arrow::Table::Make(std::make_shared(resultFields), columns); } arrow::ChunkedArray* getIndexFromLabel(arrow::Table* table, std::string_view label) { - auto field = std::find_if(table->schema()->fields().begin(), table->schema()->fields().end(), [&](std::shared_ptr const& f) { + auto field = std::ranges::find_if(table->schema()->fields(), [&](std::shared_ptr const& f) { auto caseInsensitiveCompare = [](const std::string_view& str1, const std::string& str2) { return std::ranges::equal( str1, str2, 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 c0f804b47f5af..149664c42caba 100644 --- a/Framework/Core/src/AnalysisHelpers.cxx +++ b/Framework/Core/src/AnalysisHelpers.cxx @@ -8,10 +8,133 @@ // 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/AnalysisHelpers.h" #include "Framework/ExpressionHelpers.h" +#include "ExpressionJSONHelpers.h" +#include "IndexJSONHelpers.h" + +namespace o2::soa +{ +std::vector IndexBuilder::makeBuilders(std::vector>&& tables, std::vector const& records) +{ + std::vector builders; + builders.reserve(records.size()); + auto pool = arrow::default_memory_pool(); + builders.emplace_back(IndexKind::IdxSelf, records[0].pos, pool); + if (records[0].pos >= 0) { + std::get(builders[0].builder).keyIndex = std::make_unique(tables[0]->column(records[0].pos)); + } + + for (auto i = 1U; i < records.size(); ++i) { + builders.emplace_back(records[i].kind, records[i].pos, pool, records[i].pos >= 0 ? tables[i]->column(records[i].pos) : nullptr); + } + + return builders; +} + +void IndexBuilder::resetBuilders(std::vector& builders, std::vector>&& tables) +{ + for (auto i = 0U; i < builders.size(); ++i) { + builders[i].reset(builders[i].mColumnPos >= 0 ? tables[i]->column(builders[i].mColumnPos) : nullptr); + } + + if (builders[0].mColumnPos >= 0) { + std::get(builders[0].builder).keyIndex = std::make_unique(tables[0]->column(builders[0].mColumnPos)); + } +} + +std::shared_ptr IndexBuilder::materialize(std::vector& builders, std::vector>&& tables, std::vector const& records, std::shared_ptr const& schema, bool exclusive) +{ + auto size = tables[0]->num_rows(); + if (O2_BUILTIN_UNLIKELY(builders.empty())) { + builders = makeBuilders(std::move(tables), records); + } else { + resetBuilders(builders, std::move(tables)); + } + + for (int64_t counter = 0; counter < size; ++counter) { + int64_t idx = -1; + if (std::get(builders[0].builder).keyIndex == nullptr) { + idx = counter; + } else { + idx = std::get(builders[0].builder).keyIndex->valueAt(counter); + } + + bool found = true; + std::ranges::for_each(builders, [&idx, &found](auto& builder) { found &= builder.find(idx); }); + + if (!exclusive || found) { + builders[0].fill(counter); + std::ranges::for_each(builders.begin() + 1, builders.end(), [&idx](auto& builder) { builder.fill(idx); }); + } + } + + std::vector> arrays; + arrays.reserve(builders.size()); + std::ranges::transform(builders, std::back_inserter(arrays), [](auto& builder) { return builder.result(); }); + + return arrow::Table::Make(schema, arrays); +} +} // namespace o2::soa namespace o2::framework { +std::shared_ptr makeEmptyTableImpl(const char* name, std::shared_ptr& schema) +{ + schema = schema->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{name}})); + return arrow::Table::MakeEmpty(schema).ValueOrDie(); +} + +std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, size_t nColumns, + expressions::Projector* projectors, const char* name, + std::shared_ptr& projector) +{ + if (projector == nullptr) { + projector = framework::expressions::createProjectorHelper(nColumns, projectors, fullTable->schema(), newSchema->fields()); + } + + return spawnerHelper(fullTable, newSchema, name, nColumns, projector); +} + +std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, + const char* name, size_t nColumns, + std::shared_ptr const& projector) +{ + arrow::TableBatchReader reader(*fullTable); + std::shared_ptr batch; + arrow::ArrayVector v; + std::vector chunks; + chunks.resize(nColumns); + std::vector> arrays; + + while (true) { + auto s = reader.ReadNext(&batch); + if (!s.ok()) { + throw runtime_error_f("Cannot read batches from the source table to spawn %s: %s", name, s.ToString().c_str()); + } + if (batch == nullptr) { + break; + } + try { + s = projector->Evaluate(*batch, arrow::default_memory_pool(), &v); + if (!s.ok()) { + throw runtime_error_f("Cannot apply projector to the source table of %s: %s", name, s.ToString().c_str()); + } + } catch (std::exception& e) { + throw runtime_error_f("Cannot apply projector to the source table of %s: exception caught: %s", name, e.what()); + } + + for (auto i = 0U; i < nColumns; ++i) { + chunks[i].emplace_back(v.at(i)); + } + } + + arrays.reserve(nColumns); + std::ranges::transform(chunks, std::back_inserter(arrays), [](auto&& chunk) { return std::make_shared(chunk); }); + + return arrow::Table::Make(newSchema, arrays); +} + void initializePartitionCaches(std::set const& hashes, std::shared_ptr const& schema, expressions::Filter const& filter, gandiva::NodePtr& tree, gandiva::FilterPtr& gfilter) { if (tree == nullptr) { @@ -26,4 +149,57 @@ void initializePartitionCaches(std::set const& hashes, std::shared_ptr gfilter = framework::expressions::createFilter(schema, framework::expressions::makeCondition(tree)); } } + +std::string serializeProjectors(std::vector& projectors) +{ + std::stringstream osm; + ExpressionJSONHelpers::write(osm, projectors); + return osm.str(); +} + +std::string serializeSchema(std::shared_ptr schema) +{ + std::stringstream osm; + ArrowJSONHelpers::write(osm, schema); + return osm.str(); +} + +std::string serializeIndexRecords(std::vector& irs) +{ + std::stringstream osm; + IndexJSONHelpers::write(osm, irs); + return osm.str(); +} + +std::vector> extractSources(ProcessingContext& pc, std::vector const& matchers) +{ + std::vector> tables; + tables.reserve(matchers.size()); + std::ranges::transform(matchers, std::back_inserter(tables), [&pc](auto const& matcher) { return pc.inputs().get(matcher)->asArrowTable(); }); + return tables; +} + +std::shared_ptr Spawner::materialize(ProcessingContext& pc) const +{ + auto tables = extractSources(pc, matchers); + auto fullTable = soa::ArrowHelpers::joinTables(std::move(tables), std::span{labels.begin(), labels.size()}); + if (fullTable->num_rows() == 0) { + return arrow::Table::MakeEmpty(schema).ValueOrDie(); + } + + return spawnerHelper(fullTable, schema, binding.c_str(), schema->num_fields(), projector); +} + +std::shared_ptr Builder::materialize(ProcessingContext& pc) +{ + if (builders == nullptr) { + builders = std::make_shared>(); + builders->reserve(records.size()); + } + std::shared_ptr result; + auto tables = extractSources(pc, matchers); + 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 e8c2d7acab5d2..35228bba531b0 100644 --- a/Framework/Core/src/AnalysisSupportHelpers.cxx +++ b/Framework/Core/src/AnalysisSupportHelpers.cxx @@ -11,11 +11,7 @@ #include "Framework/AnalysisSupportHelpers.h" #include "Framework/DataOutputDirector.h" -#include "Framework/OutputObjHeader.h" -#include "Framework/ControlService.h" -#include "Framework/EndOfStreamContext.h" -#include "Framework/DeviceSpec.h" -#include "Framework/TableTreeHelpers.h" +#include "Framework/DataSpecViews.h" #include "Framework/PluginManager.h" #include "Framework/ConfigContext.h" #include "WorkflowHelpers.h" @@ -29,8 +25,8 @@ namespace o2::framework std::shared_ptr AnalysisSupportHelpers::getDataOutputDirector(ConfigContext const& ctx) { auto const& options = ctx.options(); - auto const& OutputsInputs = ctx.services().get().outputsInputs; - auto const& isDangling = ctx.services().get().isDangling; + auto const& OutputsInputs = ctx.services().get().outputsInputs; + auto const& isDangling = ctx.services().get().isDangling; std::shared_ptr dod = std::make_shared(); @@ -102,7 +98,7 @@ std::shared_ptr AnalysisSupportHelpers::getDataOutputDirecto if (!keepString.empty()) { dod->reset(); std::string d("dangling"); - if (d.find(keepString) == 0) { + if (keepString.starts_with(d)) { // use the dangling outputs std::vector danglingOutputs; for (auto ii = 0u; ii < OutputsInputs.size(); ii++) { @@ -130,30 +126,11 @@ void AnalysisSupportHelpers::addMissingOutputsToReader(std::vector c std::vector const& requestedInputs, DataProcessorSpec& publisher) { - auto matchingOutputFor = [](InputSpec const& requested) { - return [&requested](OutputSpec const& provided) { - return DataSpecUtils::match(requested, provided); - }; - }; - for (InputSpec const& requested : requestedInputs) { - auto provided = std::find_if(providedOutputs.begin(), - providedOutputs.end(), - matchingOutputFor(requested)); - - if (provided != providedOutputs.end()) { - continue; - } - - auto inList = std::find_if(publisher.outputs.begin(), - publisher.outputs.end(), - matchingOutputFor(requested)); - if (inList != publisher.outputs.end()) { - continue; - } - - auto concrete = DataSpecUtils::asConcreteDataMatcher(requested); - publisher.outputs.emplace_back(concrete.origin, concrete.description, concrete.subSpec, requested.lifetime, requested.metadata); - } + requestedInputs | + views::filter_not_matching(providedOutputs) | // filter the inputs that are already provided + views::filter_not_matching(publisher.outputs) | // filter the inputs that are already covered + views::input_to_output_specs() | + sinks::append_to{publisher.outputs}; // append them to the publisher outputs } void AnalysisSupportHelpers::addMissingOutputsToSpawner(std::vector const& providedSpecials, @@ -161,25 +138,20 @@ void AnalysisSupportHelpers::addMissingOutputsToSpawner(std::vector std::vector& requestedAODs, DataProcessorSpec& publisher) { - for (auto& input : requestedSpecials) { - if (std::any_of(providedSpecials.begin(), providedSpecials.end(), [&input](auto const& x) { - return DataSpecUtils::match(input, x); - })) { - continue; - } - auto concrete = DataSpecUtils::asConcreteDataMatcher(input); - publisher.outputs.emplace_back(concrete.origin, concrete.description, concrete.subSpec); - for (auto& i : input.metadata) { - if ((i.type == VariantType::String) && (i.name.find("input:") != std::string::npos)) { - auto spec = DataSpecUtils::fromMetadataString(i.defaultValue.get()); - auto j = std::find(publisher.inputs.begin(), publisher.inputs.end(), spec); - if (j == publisher.inputs.end()) { - publisher.inputs.push_back(spec); - } - DataSpecUtils::updateInputList(requestedAODs, std::move(spec)); - } - } + requestedSpecials | + views::filter_not_matching(providedSpecials) | // filter the inputs that are already provided + views::input_to_output_specs() | + sinks::append_to{publisher.outputs}; // append them to the publisher outputs + + std::vector additionalInputs; + for (auto const& input : requestedSpecials | views::filter_not_matching(providedSpecials)) { + input.metadata | + views::filter_string_params_with("input:") | + views::params_to_input_specs() | + sinks::update_input_list{additionalInputs}; // store into a temporary } + additionalInputs | sinks::update_input_list{requestedAODs}; // update requestedAODs + additionalInputs | sinks::update_input_list{publisher.inputs}; // update publisher inputs } void AnalysisSupportHelpers::addMissingOutputsToBuilder(std::vector const& requestedSpecials, @@ -187,53 +159,31 @@ void AnalysisSupportHelpers::addMissingOutputsToBuilder(std::vector c std::vector& requestedDYNs, DataProcessorSpec& publisher) { - for (auto& input : requestedSpecials) { - auto concrete = DataSpecUtils::asConcreteDataMatcher(input); - publisher.outputs.emplace_back(concrete.origin, concrete.description, concrete.subSpec); - for (auto& i : input.metadata) { - if ((i.type == VariantType::String) && (i.name.find("input:") != std::string::npos)) { - auto spec = DataSpecUtils::fromMetadataString(i.defaultValue.get()); - auto j = std::find_if(publisher.inputs.begin(), publisher.inputs.end(), [&](auto x) { return x.binding == spec.binding; }); - if (j == publisher.inputs.end()) { - publisher.inputs.push_back(spec); - } - if (DataSpecUtils::partialMatch(spec, AODOrigins)) { - DataSpecUtils::updateInputList(requestedAODs, std::move(spec)); - } else if (DataSpecUtils::partialMatch(spec, header::DataOrigin{"DYN"})) { - DataSpecUtils::updateInputList(requestedDYNs, std::move(spec)); - } - } - } + requestedSpecials | + views::input_to_output_specs() | + sinks::append_to{publisher.outputs}; // append them to the publisher outputs + + std::vector additionalInputs; + for (auto const& input : requestedSpecials) { + input.metadata | + views::filter_string_params_with("input:") | + views::params_to_input_specs() | + sinks::update_input_list{additionalInputs}; // store into a temporary } -} -void AnalysisSupportHelpers::addMissingOutputsToAnalysisCCDBFetcher( - std::vector const& providedSpecials, - std::vector const& requestedSpecials, - std::vector& requestedAODs, - std::vector& requestedDYNs, - DataProcessorSpec& publisher) -{ - for (auto& input : requestedSpecials) { - auto concrete = DataSpecUtils::asConcreteDataMatcher(input); - publisher.outputs.emplace_back(concrete.origin, concrete.description, concrete.subSpec); - // FIXME: good enough for now... - for (auto& i : input.metadata) { - if ((i.type == VariantType::String) && (i.name.find("input:") != std::string::npos)) { - auto value = i.defaultValue.get(); - auto spec = DataSpecUtils::fromMetadataString(i.defaultValue.get()); - auto j = std::find_if(publisher.inputs.begin(), publisher.inputs.end(), [&](auto x) { return x.binding == spec.binding; }); - if (j == publisher.inputs.end()) { - publisher.inputs.push_back(spec); - } - if (DataSpecUtils::partialMatch(spec, AODOrigins)) { - DataSpecUtils::updateInputList(requestedAODs, std::move(spec)); - } else if (DataSpecUtils::partialMatch(spec, header::DataOrigin{"DYN"})) { - DataSpecUtils::updateInputList(requestedDYNs, std::move(spec)); - } - } - } - } + additionalInputs | sinks::update_input_list{publisher.inputs}; // update publisher inputs + // 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 | + 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 } // ============================================================================= @@ -244,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), }; @@ -255,7 +205,7 @@ DataProcessorSpec AnalysisSupportHelpers::getOutputObjHistSink(ConfigContext con DataProcessorSpec AnalysisSupportHelpers::getGlobalAODSink(ConfigContext const& ctx) { - auto& ac = ctx.services().get(); + auto& ac = ctx.services().get(); // the command line options relevant for the writer are global // see runDataProcessing.h diff --git a/Framework/Core/src/AnalysisTask.cxx b/Framework/Core/src/AnalysisTask.cxx new file mode 100644 index 0000000000000..993c597a95f49 --- /dev/null +++ b/Framework/Core/src/AnalysisTask.cxx @@ -0,0 +1,68 @@ +// 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 + +namespace o2::framework +{ +/// Convert a CamelCase task struct name to snake-case task name +std::string type_to_task_name(std::string_view const& camelCase) +{ + std::string result; + result.reserve(camelCase.size() * 2 + 2); + + // The first character is always -. + result += "-"; + result += static_cast(std::tolower(camelCase[0])); + + for (auto it = camelCase.begin() + 1; it != camelCase.end(); ++it) { + if (std::isupper(*it) && *(it - 1) != '-') { + result += '-'; + } + result += static_cast(std::tolower(*it)); + } + // Post-process to consolidate common ALICE abbreviations + // Process backwards to handle patterns correctly + static const struct { + std::string_view pattern; + std::string_view replacement; + } abbreviations[] = { + {"-e-m-c-a-l", "-emcal"}, + {"-e-m-c", "-emc"} + }; + + std::string consolidated; + consolidated.reserve(result.size()); + + for (int i = result.size() - 1; i >= 0;) { + bool matched = false; + + for (const auto& abbr : abbreviations) { + int startPos = i - abbr.pattern.size() + 1; + if (startPos >= 0 && result.compare(startPos, abbr.pattern.size(), abbr.pattern.data()) == 0) { + consolidated.insert(0, abbr.replacement); + i = startPos - 1; + matched = true; + break; + } + } + + if (!matched) { + consolidated.insert(0, 1, result[i]); + --i; + } + } + if (consolidated[0] == '-') { + return std::string(consolidated.data() + 1); + } + + return consolidated; +} +} // namespace o2::framework diff --git a/Framework/Core/src/ArrowSupport.cxx b/Framework/Core/src/ArrowSupport.cxx index 0e524da280598..eecff4ce87c74 100644 --- a/Framework/Core/src/ArrowSupport.cxx +++ b/Framework/Core/src/ArrowSupport.cxx @@ -10,20 +10,16 @@ // or submit itself to any jurisdiction. #include "ArrowSupport.h" -#include "Framework/AODReaderHelpers.h" #include "Framework/ArrowContext.h" #include "Framework/ArrowTableSlicingCache.h" -#include "Framework/SliceCache.h" #include "Framework/DataProcessor.h" +#include "Framework/CommonDataProcessors.h" #include "Framework/DataProcessingStats.h" #include "Framework/ServiceRegistry.h" #include "Framework/ConfigContext.h" -#include "Framework/CommonDataProcessors.h" #include "Framework/DataSpecUtils.h" #include "Framework/DataSpecViews.h" #include "Framework/DeviceSpec.h" -#include "Framework/EndOfStreamContext.h" -#include "Framework/Tracing.h" #include "Framework/DeviceMetricsInfo.h" #include "Framework/DeviceMetricsHelper.h" #include "Framework/DeviceInfo.h" @@ -37,11 +33,12 @@ #include "Framework/ServiceRegistryRef.h" #include "Framework/ServiceRegistryHelpers.h" #include "Framework/Signpost.h" +#include "Framework/DefaultsHelpers.h" +#include "Framework/ConfigParamsHelper.h" #include "CommonMessageBackendsHelpers.h" #include #include "Headers/DataHeader.h" -#include "Headers/DataHeaderHelpers.h" #include #include @@ -54,7 +51,6 @@ O2_DECLARE_DYNAMIC_LOG(rate_limiting); namespace o2::framework { - class EndOfStreamContext; class ProcessingContext; @@ -70,7 +66,7 @@ enum struct RateLimitingState { struct RateLimitConfig { int64_t maxMemory = 2000; - int64_t maxTimeframes = 0; + int64_t maxTimeframes = 1000; }; struct MetricIndices { @@ -82,6 +78,11 @@ struct MetricIndices { size_t shmOfferBytesConsumed = -1; size_t timeframesRead = -1; size_t timeframesConsumed = -1; + size_t timeframesExpired = -1; + // Timeslices counting + size_t timeslicesStarted = -1; + size_t timeslicesExpired = -1; + size_t timeslicesDone = -1; }; std::vector createDefaultIndices(std::vector& allDevicesMetrics) @@ -89,24 +90,166 @@ std::vector createDefaultIndices(std::vector& std::vector results; for (auto& info : allDevicesMetrics) { - MetricIndices indices; - indices.arrowBytesCreated = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-created"); - indices.arrowBytesDestroyed = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-destroyed"); - indices.arrowMessagesCreated = DeviceMetricsHelper::bookNumericMetric(info, "arrow-messages-created"); - indices.arrowMessagesDestroyed = DeviceMetricsHelper::bookNumericMetric(info, "arrow-messages-destroyed"); - indices.arrowBytesExpired = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-expired"); - indices.shmOfferBytesConsumed = DeviceMetricsHelper::bookNumericMetric(info, "shm-offer-bytes-consumed"); - indices.timeframesRead = DeviceMetricsHelper::bookNumericMetric(info, "df-sent"); - indices.timeframesConsumed = DeviceMetricsHelper::bookNumericMetric(info, "consumed-timeframes"); - results.push_back(indices); + results.emplace_back(MetricIndices{ + .arrowBytesCreated = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-created"), + .arrowBytesDestroyed = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-destroyed"), + .arrowMessagesCreated = DeviceMetricsHelper::bookNumericMetric(info, "arrow-messages-created"), + .arrowMessagesDestroyed = DeviceMetricsHelper::bookNumericMetric(info, "arrow-messages-destroyed"), + .arrowBytesExpired = DeviceMetricsHelper::bookNumericMetric(info, "arrow-bytes-expired"), + .shmOfferBytesConsumed = DeviceMetricsHelper::bookNumericMetric(info, "shm-offer-bytes-consumed"), + .timeframesRead = DeviceMetricsHelper::bookNumericMetric(info, "df-sent"), + .timeframesConsumed = DeviceMetricsHelper::bookNumericMetric(info, "consumed-timeframes"), + .timeframesExpired = DeviceMetricsHelper::bookNumericMetric(info, "expired-timeframes"), + .timeslicesStarted = DeviceMetricsHelper::bookNumericMetric(info, "timeslices-started"), + .timeslicesExpired = DeviceMetricsHelper::bookNumericMetric(info, "timeslices-expired"), + .timeslicesDone = DeviceMetricsHelper::bookNumericMetric(info, "timeslices-done"), + }); } return results; } -uint64_t calculateAvailableSharedMemory(ServiceRegistryRef registry) +struct ResourceState { + int64_t available; + int64_t offered = 0; + int64_t lastDeviceOffered = 0; +}; +struct ResourceStats { + int64_t enoughCount; /// How many times the resources were enough + int64_t lowCount; /// How many times the resources were not enough +}; +struct ResourceSpec { + char const* name; + char const* unit; + char const* api; /// The callback to give resources to a device + int64_t maxAvailable; /// Maximum available quantity for a resource + int64_t maxQuantum; /// Largest offer which can be given + int64_t minQuantum; /// Smallest offer which can be given + int64_t metricOfferScaleFactor; /// The scale factor between the metric accounting and offers accounting +}; + +auto offerResources(ResourceState& resourceState, + ResourceSpec const& resourceSpec, + ResourceStats& resourceStats, + std::vector const& specs, + std::vector const& infos, + DevicesManager& manager, + int64_t offerConsumedCurrentValue, + int64_t offerExpiredCurrentValue, + int64_t acquiredResourceCurrentValue, + int64_t disposedResourceCurrentValue, + size_t timestamp, + DeviceMetricsInfo& driverMetrics, + std::function& availableResourceMetric, + std::function& unusedOfferedResourceMetric, + std::function& offeredResourceMetric, + void* signpostId) -> void { - return registry.get().maxMemory; -} + O2_SIGNPOST_ID_FROM_POINTER(sid, rate_limiting, signpostId); + /// We loop over the devices, starting from where we stopped last time + /// offering the minimum offer to each one + int64_t lastCandidate = -1; + int64_t possibleOffer = resourceSpec.minQuantum; + + for (size_t di = 0; di < specs.size(); di++) { + if (resourceState.available < possibleOffer) { + if (resourceStats.lowCount == 0) { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "not enough", + "We do not have enough %{public}s (%llu %{public}s) to offer %llu %{public}s. Total offerings %{bytes}llu %{string}s.", + resourceSpec.name, resourceState.available, resourceSpec.unit, + possibleOffer, resourceSpec.unit, + resourceState.offered, resourceSpec.unit); + } + resourceStats.lowCount++; + resourceStats.enoughCount = 0; + break; + } else { + if (resourceStats.enoughCount == 0) { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "enough", + "We are back in a state where we enough %{public}s: %llu %{public}s", + resourceSpec.name, + resourceState.available, + resourceSpec.unit); + } + resourceStats.lowCount = 0; + resourceStats.enoughCount++; + } + size_t candidate = (resourceState.lastDeviceOffered + di) % specs.size(); + + auto& info = infos[candidate]; + // Do not bother for inactive devices + // FIXME: there is probably a race condition if the device died and we did not + // took notice yet... + if (info.active == false || info.readyToQuit) { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", + "Device %s is inactive not offering %{public}s to it.", + specs[candidate].name.c_str(), resourceSpec.name); + continue; + } + if (specs[candidate].name != "internal-dpl-aod-reader") { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", + "Device %s is not a reader. Not offering %{public}s to it.", + specs[candidate].name.c_str(), + resourceSpec.name); + continue; + } + possibleOffer = std::min(resourceSpec.maxQuantum, resourceState.available); + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", + "Offering %llu %{public}s out of %llu to %{public}s", + possibleOffer, resourceSpec.unit, resourceState.available, specs[candidate].id.c_str()); + manager.queueMessage(specs[candidate].id.c_str(), fmt::format(fmt::runtime(resourceSpec.api), possibleOffer).data()); + resourceState.available -= possibleOffer; + resourceState.offered += possibleOffer; + lastCandidate = candidate; + } + // We had at least a valid candidate, so + // next time we offer to the next device. + if (lastCandidate >= 0) { + resourceState.lastDeviceOffered = lastCandidate + 1; + } + + // unusedOfferedSharedMemory is the amount of memory which was offered and which we know it was + // not used so far. So we need to account for the amount which got actually read (readerBytesCreated) + // and the amount which we know was given back. + static int64_t lastResourceOfferConsumed = 0; + static int64_t lastUnusedOfferedResource = 0; + if (offerConsumedCurrentValue != lastResourceOfferConsumed) { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", + "Offer consumed so far %llu", offerConsumedCurrentValue); + lastResourceOfferConsumed = offerConsumedCurrentValue; + } + int unusedOfferedResource = (resourceState.offered - (offerExpiredCurrentValue + offerConsumedCurrentValue) / resourceSpec.metricOfferScaleFactor); + if (lastUnusedOfferedResource != unusedOfferedResource) { + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", + "unusedOfferedResource(%{public}s):%{bytes}d = offered:%{bytes}llu - (expired:%{bytes}llu + consumed:%{bytes}llu) / %lli", + resourceSpec.name, + unusedOfferedResource, resourceState.offered, + offerExpiredCurrentValue / resourceSpec.metricOfferScaleFactor, + offerConsumedCurrentValue / resourceSpec.metricOfferScaleFactor, + resourceSpec.metricOfferScaleFactor); + lastUnusedOfferedResource = unusedOfferedResource; + } + // availableSharedMemory is the amount of memory which we know is available to be offered. + // We subtract the amount which we know was already offered but it's unused and we then balance how + // much was created with how much was destroyed. + resourceState.available = resourceSpec.maxAvailable + ((disposedResourceCurrentValue - acquiredResourceCurrentValue) / resourceSpec.metricOfferScaleFactor) - unusedOfferedResource; + availableResourceMetric(driverMetrics, resourceState.available, timestamp); + unusedOfferedResourceMetric(driverMetrics, unusedOfferedResource, timestamp); + + offeredResourceMetric(driverMetrics, resourceState.offered, timestamp); +}; + +auto processTimeslices = [](size_t index, DeviceMetricsInfo& deviceMetrics, bool& changed, + int64_t& totalMetricValue, size_t& lastTimestamp) { + assert(index < deviceMetrics.metrics.size()); + changed |= deviceMetrics.changed[index]; + MetricInfo info = deviceMetrics.metrics[index]; + assert(info.storeIdx < deviceMetrics.uint64Metrics.size()); + auto& data = deviceMetrics.uint64Metrics[info.storeIdx]; + auto value = (int64_t)data[(info.pos - 1) % data.size()]; + totalMetricValue += value; + auto const& timestamps = DeviceMetricsHelper::getTimestampsStore(deviceMetrics)[info.storeIdx]; + lastTimestamp = std::max(lastTimestamp, timestamps[(info.pos - 1) % data.size()]); +}; o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() { @@ -134,25 +277,47 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() int64_t totalMessagesDestroyed = 0; int64_t totalTimeframesRead = 0; int64_t totalTimeframesConsumed = 0; + int64_t totalTimeframesExpired = 0; + int64_t totalTimeslicesStarted = 0; + int64_t totalTimeslicesDone = 0; + int64_t totalTimeslicesExpired = 0; auto &driverMetrics = sm.driverMetricsInfo; auto &allDeviceMetrics = sm.deviceMetricsInfos; auto &specs = sm.deviceSpecs; auto &infos = sm.deviceInfos; - O2_SIGNPOST_ID_FROM_POINTER(sid, rate_limiting, &sm); + + // Aggregated driver metrics for timeslice rate limiting + auto createUint64DriverMetric = [&driverMetrics](char const*name) -> auto { + return DeviceMetricsHelper::createNumericMetric(driverMetrics, name); + }; + auto createIntDriverMetric = [&driverMetrics](char const*name) -> auto { + return DeviceMetricsHelper::createNumericMetric(driverMetrics, name); + }; static auto stateMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "rate-limit-state"); static auto totalBytesCreatedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-arrow-bytes-created"); static auto shmOfferConsumedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-shm-offer-bytes-consumed"); + // These are really to monitor the rate limiting static auto unusedOfferedSharedMemoryMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-unused-offered-shared-memory"); + static auto unusedOfferedTimeslicesMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-unused-offered-timeslices"); static auto availableSharedMemoryMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-available-shared-memory"); + static auto availableTimeslicesMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-available-timeslices"); static auto offeredSharedMemoryMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-offered-shared-memory"); + static auto offeredTimeslicesMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-offered-timeslices"); + static auto totalBytesDestroyedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-arrow-bytes-destroyed"); static auto totalBytesExpiredMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-arrow-bytes-expired"); static auto totalMessagesCreatedMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "total-arrow-messages-created"); 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 totalTimeslicesInFlightMetric = createIntDriverMetric("total-timeslices-in-flight"); + static auto totalBytesDeltaMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "arrow-bytes-delta"); static auto changedCountMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "changed-metrics-count"); static auto totalSignalsMetric = DeviceMetricsHelper::createNumericMetric(driverMetrics, "aod-reader-signals"); @@ -267,6 +432,21 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() auto const& timestamps = DeviceMetricsHelper::getTimestampsStore(deviceMetrics)[info.storeIdx]; lastTimestamp = std::max(lastTimestamp, timestamps[(info.pos - 1) % data.size()]); } + { + size_t index = indices.timeframesExpired; + assert(index < deviceMetrics.metrics.size()); + changed |= deviceMetrics.changed[index]; + MetricInfo info = deviceMetrics.metrics[index]; + assert(info.storeIdx < deviceMetrics.uint64Metrics.size()); + auto& data = deviceMetrics.uint64Metrics[info.storeIdx]; + auto value = (int64_t)data[(info.pos - 1) % data.size()]; + totalTimeframesExpired += value; + auto const& timestamps = DeviceMetricsHelper::getTimestampsStore(deviceMetrics)[info.storeIdx]; + lastTimestamp = std::max(lastTimestamp, timestamps[(info.pos - 1) % data.size()]); + } + processTimeslices(indices.timeslicesStarted, deviceMetrics, changed, totalTimeslicesStarted, lastTimestamp); + processTimeslices(indices.timeslicesExpired, deviceMetrics, changed, totalTimeslicesExpired, lastTimestamp); + processTimeslices(indices.timeslicesDone, deviceMetrics, changed, totalTimeslicesDone, lastTimestamp); } static uint64_t unchangedCount = 0; if (changed) { @@ -278,100 +458,61 @@ 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); + totalTimeslicesInFlightMetric(driverMetrics, (int)(totalTimeslicesStarted - totalTimeslicesDone), timestamp); totalBytesDeltaMetric(driverMetrics, totalBytesCreated - totalBytesExpired - totalBytesDestroyed, timestamp); } else { unchangedCount++; } changedCountMetric(driverMetrics, unchangedCount, timestamp); - auto maxTimeframes = registry.get().maxTimeframes; - if (maxTimeframes && (totalTimeframesRead - totalTimeframesConsumed) > maxTimeframes) { - return; - } - static int64_t MAX_SHARED_MEMORY = calculateAvailableSharedMemory(registry); - constexpr int64_t MAX_QUANTUM_SHARED_MEMORY = 100; - constexpr int64_t MIN_QUANTUM_SHARED_MEMORY = 50; - - static int64_t availableSharedMemory = MAX_SHARED_MEMORY; - static int64_t offeredSharedMemory = 0; - static int64_t lastDeviceOffered = 0; - /// We loop over the devices, starting from where we stopped last time - /// offering MIN_QUANTUM_SHARED_MEMORY of shared memory to each reader. - int64_t lastCandidate = -1; - static int enoughSharedMemoryCount = availableSharedMemory - MIN_QUANTUM_SHARED_MEMORY > 0 ? 1 : 0; - static int lowSharedMemoryCount = availableSharedMemory - MIN_QUANTUM_SHARED_MEMORY > 0 ? 0 : 1; - int64_t possibleOffer = MIN_QUANTUM_SHARED_MEMORY; - for (size_t di = 0; di < specs.size(); di++) { - if (availableSharedMemory < possibleOffer) { - if (lowSharedMemoryCount == 0) { - O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "not enough", - "We do not have enough shared memory (%{bytes}llu MB) to offer %{bytes}llu MB. Total offerings %{bytes}llu", - availableSharedMemory, possibleOffer, offeredSharedMemory); - } - lowSharedMemoryCount++; - enoughSharedMemoryCount = 0; - break; - } else { - if (enoughSharedMemoryCount == 0) { - O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "enough", - "We are back in a state where we enough shared memory: %{bytes}llu MB", availableSharedMemory); - } - enoughSharedMemoryCount++; - lowSharedMemoryCount = 0; - } - size_t candidate = (lastDeviceOffered + di) % specs.size(); - - auto& info = infos[candidate]; - // Do not bother for inactive devices - // FIXME: there is probably a race condition if the device died and we did not - // took notice yet... - if (info.active == false || info.readyToQuit) { - continue; - } - if (specs[candidate].name != "internal-dpl-aod-reader") { - continue; - } - possibleOffer = std::min(MAX_QUANTUM_SHARED_MEMORY, availableSharedMemory); - O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", - "Offering %{bytes}llu MB out of %{bytes}llu to %{public}s", - possibleOffer, availableSharedMemory, specs[candidate].id.c_str()); - manager.queueMessage(specs[candidate].id.c_str(), fmt::format("/shm-offer {}", possibleOffer).data()); - availableSharedMemory -= possibleOffer; - offeredSharedMemory += possibleOffer; - lastCandidate = candidate; - } - // We had at least a valid candidate, so - // next time we offer to the next device. - if (lastCandidate >= 0) { - lastDeviceOffered = lastCandidate + 1; - } - - // unusedOfferedSharedMemory is the amount of memory which was offered and which we know it was - // not used so far. So we need to account for the amount which got actually read (readerBytesCreated) - // and the amount which we know was given back. - static int64_t lastShmOfferConsumed = 0; - static int64_t lastUnusedOfferedMemory = 0; - if (shmOfferBytesConsumed != lastShmOfferConsumed) { - O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", - "Offer consumed so far %{bytes}llu", shmOfferBytesConsumed); - lastShmOfferConsumed = shmOfferBytesConsumed; - } - int unusedOfferedMemory = (offeredSharedMemory - (totalBytesExpired + shmOfferBytesConsumed) / 1000000); - if (lastUnusedOfferedMemory != unusedOfferedMemory) { - O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", - "unusedOfferedMemory:%{bytes}d = offered:%{bytes}llu - (expired:%{bytes}llu + consumed:%{bytes}llu) / 1000000", - unusedOfferedMemory, offeredSharedMemory, totalBytesExpired / 1000000, shmOfferBytesConsumed / 1000000); - lastUnusedOfferedMemory = unusedOfferedMemory; - } - // availableSharedMemory is the amount of memory which we know is available to be offered. - // We subtract the amount which we know was already offered but it's unused and we then balance how - // much was created with how much was destroyed. - availableSharedMemory = MAX_SHARED_MEMORY + ((totalBytesDestroyed - totalBytesCreated) / 1000000) - unusedOfferedMemory; - availableSharedMemoryMetric(driverMetrics, availableSharedMemory, timestamp); - unusedOfferedSharedMemoryMetric(driverMetrics, unusedOfferedMemory, timestamp); - - offeredSharedMemoryMetric(driverMetrics, offeredSharedMemory, timestamp); }, + static const ResourceSpec shmResourceSpec{ + .name = "shared memory", + .unit = "MB", + .api = "/shm-offer {}", + .maxAvailable = (int64_t)registry.get().maxMemory, + .maxQuantum = 100, + .minQuantum = 50, + .metricOfferScaleFactor = 1000000, + }; + static const ResourceSpec timesliceResourceSpec{ + .name = "timeslice", + .unit = "timeslices", + .api = "/timeslice-offer {}", + .maxAvailable = (int64_t)registry.get().maxTimeframes, + .maxQuantum = 1, + .minQuantum = 1, + .metricOfferScaleFactor = 1, + }; + static ResourceState shmResourceState{ + .available = shmResourceSpec.maxAvailable, + }; + static ResourceState timesliceResourceState{ + .available = timesliceResourceSpec.maxAvailable, + }; + static ResourceStats shmResourceStats{ + .enoughCount = shmResourceState.available - shmResourceSpec.minQuantum > 0 ? 1 : 0, + .lowCount = shmResourceState.available - shmResourceSpec.minQuantum > 0 ? 0 : 1 + }; + static ResourceStats timesliceResourceStats{ + .enoughCount = shmResourceState.available - shmResourceSpec.minQuantum > 0 ? 1 : 0, + .lowCount = shmResourceState.available - shmResourceSpec.minQuantum > 0 ? 0 : 1 + }; + + offerResources(timesliceResourceState, timesliceResourceSpec, timesliceResourceStats, + specs, infos, manager, totalTimeframesConsumed, totalTimeslicesExpired, + totalTimeslicesStarted, totalTimeslicesDone, timestamp, driverMetrics, + availableTimeslicesMetric, unusedOfferedTimeslicesMetric, offeredTimeslicesMetric, + (void*)&sm); + + offerResources(shmResourceState, shmResourceSpec, shmResourceStats, + specs, infos, manager, shmOfferBytesConsumed, totalBytesExpired, + totalBytesCreated, totalBytesDestroyed, timestamp, driverMetrics, + availableSharedMemoryMetric, unusedOfferedSharedMemoryMetric, offeredSharedMemoryMetric, + (void*)&sm); }, .postDispatching = [](ProcessingContext& ctx, void* service) { using DataHeader = o2::header::DataHeader; auto* arrow = reinterpret_cast(service); @@ -390,16 +531,10 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() dh->dataOrigin.str, dh->dataDescription.str); continue; } - bool forwarded = false; - for (auto const& forward : ctx.services().get().forwards) { - if (DataSpecUtils::match(forward.matcher, *dh)) { - forwarded = true; - break; - } - } + bool forwarded = std::ranges::any_of(ctx.services().get().forwards, [&dh](auto const& forward) { return DataSpecUtils::match(forward.matcher, *dh); }); if (forwarded) { O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "offer", - "Message %{public}.4s/%{public}16.s is forwarded so we are not returning its memory.", + "Message %{public}.4s/%{public}.16s is forwarded so we are not returning its memory.", dh->dataOrigin.str, dh->dataDescription.str); continue; } @@ -424,176 +559,185 @@ o2::framework::ServiceSpec ArrowSupport::arrowBackendSpec() if (dc.options.count("aod-memory-rate-limit") && dc.options["aod-memory-rate-limit"].defaulted() == false) { config->maxMemory = std::stoll(dc.options["aod-memory-rate-limit"].as()) / 1000000; } else { - config->maxMemory = readers * 500; + config->maxMemory = readers * 2000; } - if (dc.options.count("timeframes-rate-limit") && dc.options["timeframes-rate-limit"].as() == "readers") { - config->maxTimeframes = readers; - } else { + if (dc.options.count("timeframes-rate-limit") && dc.options["timeframes-rate-limit"].defaulted() == false) { config->maxTimeframes = std::stoll(dc.options["timeframes-rate-limit"].as()); + } else { + config->maxTimeframes = readers * DefaultsHelpers::pipelineLength(dc); } static bool once = false; // Until we guarantee this is called only once... if (!once) { O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); O2_SIGNPOST_EVENT_EMIT_INFO(rate_limiting, sid, "setup", - "Rate limiting set up at %{bytes}llu MB distributed over %d readers", - config->maxMemory, readers); + "Rate limiting set up at %{bytes}llu MB and %llu timeframes distributed over %d readers", + config->maxMemory, config->maxTimeframes, readers); registry.registerService(ServiceRegistryHelpers::handleForService(config)); once = true; } }, .adjustTopology = [](WorkflowSpecNode& node, ConfigContext const& ctx) { auto& workflow = node.specs; - auto spawner = std::find_if(workflow.begin(), workflow.end(), [](DataProcessorSpec const& spec) { return spec.name == "internal-dpl-aod-spawner"; }); - auto analysisCCDB = std::find_if(workflow.begin(), workflow.end(), [](DataProcessorSpec const& spec) { return spec.name == "internal-dpl-aod-ccdb"; }); - auto builder = std::find_if(workflow.begin(), workflow.end(), [](DataProcessorSpec const& spec) { return spec.name == "internal-dpl-aod-index-builder"; }); - auto reader = std::find_if(workflow.begin(), workflow.end(), [](DataProcessorSpec const& spec) { return spec.name == "internal-dpl-aod-reader"; }); - auto writer = std::find_if(workflow.begin(), workflow.end(), [](DataProcessorSpec const& spec) { return spec.name == "internal-dpl-aod-writer"; }); - auto &ac = ctx.services().get(); - ac.requestedAODs.clear(); - ac.requestedDYNs.clear(); - ac.providedDYNs.clear(); - ac.providedTIMs.clear(); - ac.requestedTIMs.clear(); - + auto& dec = ctx.services().get(); + dec.requestedAODs.clear(); + dec.requestedDYNs.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 - ac.requestedIDXs.clear(); - for (auto& d : workflow) { - if (d.name == builder->name) { - continue; - } - for (auto& i : d.inputs) { - if (DataSpecUtils::partialMatch(i, header::DataOrigin{"IDX"})) { - auto copy = i; - DataSpecUtils::updateInputList(ac.requestedIDXs, std::move(copy)); - } - } + dec.requestedIDXs.clear(); + dec.providedIDXs.clear(); + for (auto& d : workflow | views::exclude_by_name(builder->name)) { + d.inputs | + 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(); - // replace AlgorithmSpec - // FIXME: it should be made more generic, so it does not need replacement... - builder->algorithm = readers::AODReaderHelpers::indexBuilderCallback(ac.requestedIDXs); - AnalysisSupportHelpers::addMissingOutputsToBuilder(ac.requestedIDXs, ac.requestedAODs, ac.requestedDYNs, *builder); - } - - if (spawner != workflow.end()) { - // collect currently requested DYNs - for (auto& d : workflow) { - if (d.name == spawner->name) { - continue; - } - for (auto const& i : d.inputs) { - if (DataSpecUtils::partialMatch(i, header::DataOrigin{"DYN"})) { - auto copy = i; - DataSpecUtils::updateInputList(ac.requestedDYNs, std::move(copy)); - } - } - for (auto const& o : d.outputs) { - if (DataSpecUtils::partialMatch(o, header::DataOrigin{"DYN"})) { - ac.providedDYNs.emplace_back(o); - } - } + 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); } - std::sort(ac.requestedDYNs.begin(), ac.requestedDYNs.end(), inputSpecLessThan); - std::sort(ac.providedDYNs.begin(), ac.providedDYNs.end(), outputSpecLessThan); - ac.spawnerInputs.clear(); - for (auto& input : ac.requestedDYNs) { - if (std::none_of(ac.providedDYNs.begin(), ac.providedDYNs.end(), [&input](auto const& x) { return DataSpecUtils::match(input, x); })) { - ac.spawnerInputs.emplace_back(input); - } - } - // recreate inputs and outputs - spawner->outputs.clear(); - spawner->inputs.clear(); - // replace AlgorithmSpec - // FIXME: it should be made more generic, so it does not need replacement... - spawner->algorithm = readers::AODReaderHelpers::aodSpawnerCallback(ac.spawnerInputs); - AnalysisSupportHelpers::addMissingOutputsToSpawner({}, ac.spawnerInputs, ac.requestedAODs, *spawner); } + 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::partial_match_filter(header::DataOrigin{"ATIM"}) | sinks::update_input_list{ac.requestedTIMs}; - d.outputs | views::partial_match_filter(header::DataOrigin{"ATIM"}) | sinks::append_to{ac.providedTIMs}; + 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::sort(ac.requestedTIMs.begin(), ac.requestedTIMs.end(), inputSpecLessThan); - std::sort(ac.providedTIMs.begin(), ac.providedTIMs.end(), outputSpecLessThan); + std::ranges::sort(dec.requestedTIMs, inputSpecLessThan); + std::ranges::sort(dec.providedTIMs, outputSpecLessThan); // Use ranges::to> in C++23... - ac.analysisCCDBInputs.clear(); - ac.requestedTIMs | views::filter_not_matching(ac.providedTIMs) | sinks::append_to{ac.analysisCCDBInputs}; + 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(); - // replace AlgorithmSpec - // FIXME: it should be made more generic, so it does not need replacement... - // FIXME how can I make the lookup depend on DYN tables as well?? + 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 analysisCCDB->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); - AnalysisSupportHelpers::addMissingOutputsToAnalysisCCDBFetcher({}, ac.analysisCCDBInputs, ac.requestedAODs, ac.requestedDYNs, *analysisCCDB); } + 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::filter_with_params_by_name("projectors") | + sinks::update_input_list{dec.requestedDYNs}; + d.outputs | + views::filter_with_params_by_name("projectors") | + sinks::append_to{dec.providedDYNs}; + } + std::ranges::sort(dec.requestedDYNs, inputSpecLessThan); + std::ranges::sort(dec.providedDYNs, outputSpecLessThan); + dec.spawnerInputs.clear(); + dec.requestedDYNs | + views::filter_not_matching(dec.providedDYNs) | + sinks::append_to{dec.spawnerInputs}; + // recreate inputs and outputs + spawner->outputs.clear(); + spawner->inputs.clear(); + AnalysisSupportHelpers::addMissingOutputsToSpawner({}, dec.spawnerInputs, dec.requestedAODs, *spawner); + if (!spawner->inputs.empty()) { + // load real AlgorithmSpec before deployment + spawner->algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkOnDemandTablesSupport", "ExtendedTableSpawner", ctx); + } + } + + 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); } + // removing writer would invalidate the reader iterator if it was created before + auto reader = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { return spec.name.starts_with("internal-dpl-aod-reader"); }); + if (reader != workflow.end()) { // If reader and/or builder were adjusted, remove unneeded outputs // update currently requested AODs for (auto& d : workflow) { - for (auto const& i : d.inputs) { - if (DataSpecUtils::partialMatch(i, AODOrigins)) { - auto copy = i; - DataSpecUtils::updateInputList(ac.requestedAODs, std::move(copy)); - } - } + d.inputs | + views::partial_match_filter(AODOrigins) | + sinks::update_input_list{dec.requestedAODs}; } // remove unmatched outputs auto o_end = std::remove_if(reader->outputs.begin(), reader->outputs.end(), [&](OutputSpec const& o) { - return !DataSpecUtils::partialMatch(o, o2::header::DataDescription{"TFNumber"}) && !DataSpecUtils::partialMatch(o, o2::header::DataDescription{"TFFilename"}) && std::none_of(ac.requestedAODs.begin(), ac.requestedAODs.end(), [&](InputSpec const& i) { return DataSpecUtils::match(i, o); }); + return !DataSpecUtils::partialMatch(o, o2::header::DataDescription{"TFNumber"}) && !DataSpecUtils::partialMatch(o, o2::header::DataDescription{"TFFilename"}) && std::none_of(dec.requestedAODs.begin(), dec.requestedAODs.end(), [&](InputSpec const& i) { return DataSpecUtils::match(i, o); }); }); reader->outputs.erase(o_end, reader->outputs.end()); if (reader->outputs.empty()) { // nothing to read workflow.erase(reader); + } else { + // load reader algorithm before deployment + auto tfnsource = std::ranges::find_if(workflow, [](DataProcessorSpec const& spec) { + 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 already set in injectServiceDevices } } + WorkflowHelpers::injectAODWriter(workflow, ctx); - - // replace writer as some outputs may have become dangling and some are now consumed - auto [outputsInputs, isDangling] = WorkflowHelpers::analyzeOutputs(workflow); - - // create DataOutputDescriptor - std::shared_ptr dod = AnalysisSupportHelpers::getDataOutputDirector(ctx); - - // select outputs of type AOD which need to be saved - // ATTENTION: if there are dangling outputs the getGlobalAODSink - // has to be created in any case! - ac.outputsInputsAOD.clear(); - - for (auto ii = 0u; ii < outputsInputs.size(); ii++) { - if (DataSpecUtils::partialMatch(outputsInputs[ii], extendedAODOrigins)) { - auto ds = dod->getDataOutputDescriptors(outputsInputs[ii]); - if (!ds.empty() || isDangling[ii]) { - ac.outputsInputsAOD.emplace_back(outputsInputs[ii]); - } - } - } - - // file sink for any AOD output - if (!ac.outputsInputsAOD.empty()) { - // add TFNumber and TFFilename as input to the writer - ac.outputsInputsAOD.emplace_back("tfn", "TFN", "TFNumber"); - ac.outputsInputsAOD.emplace_back("tff", "TFF", "TFFilename"); - workflow.push_back(AnalysisSupportHelpers::getGlobalAODSink(ctx)); - } // Move the dummy sink at the end, if needed for (size_t i = 0; i < workflow.size(); ++i) { if (workflow[i].name == "internal-dpl-injected-dummy-sink") { @@ -629,7 +773,7 @@ o2::framework::ServiceSpec ArrowSupport::arrowTableSlicingCacheSpec() auto& caches = service->bindingsKeys; for (auto i = 0u; i < caches.size(); ++i) { if (caches[i].enabled && pc.inputs().getPos(caches[i].binding.c_str()) >= 0) { - auto status = service->updateCacheEntry(i, pc.inputs().get(caches[i].binding.c_str())->asArrowTable()); + auto status = service->updateCacheEntry(i, pc.inputs().get(caches[i].matcher)->asArrowTable()); if (!status.ok()) { throw runtime_error_f("Failed to update slice cache for %s/%s", caches[i].binding.c_str(), caches[i].key.c_str()); } @@ -638,7 +782,7 @@ o2::framework::ServiceSpec ArrowSupport::arrowTableSlicingCacheSpec() auto& unsortedCaches = service->bindingsKeysUnsorted; for (auto i = 0u; i < unsortedCaches.size(); ++i) { if (unsortedCaches[i].enabled && pc.inputs().getPos(unsortedCaches[i].binding.c_str()) >= 0) { - auto status = service->updateCacheEntryUnsorted(i, pc.inputs().get(unsortedCaches[i].binding.c_str())->asArrowTable()); + auto status = service->updateCacheEntryUnsorted(i, pc.inputs().get(unsortedCaches[i].matcher)->asArrowTable()); if (!status.ok()) { throw runtime_error_f("failed to update slice cache (unsorted) for %s/%s", unsortedCaches[i].binding.c_str(), unsortedCaches[i].key.c_str()); } diff --git a/Framework/Core/src/ArrowTableSlicingCache.cxx b/Framework/Core/src/ArrowTableSlicingCache.cxx index 0d06a926dd930..5162c698a1d66 100644 --- a/Framework/Core/src/ArrowTableSlicingCache.cxx +++ b/Framework/Core/src/ArrowTableSlicingCache.cxx @@ -19,24 +19,38 @@ namespace o2::framework { -void updatePairList(Cache& list, std::string const& binding, std::string const& key, bool enabled = true) +namespace { - auto locate = std::find_if(list.begin(), list.end(), [&binding, &key](auto const& entry) { return (entry.binding == binding) && (entry.key == key); }); +std::shared_ptr GetColumnByNameCI(std::shared_ptr const& table, std::string const& key) +{ + auto const& fields = table->schema()->fields(); + auto target = std::find_if(fields.begin(), fields.end(), [&key](std::shared_ptr const& field) { + return [](std::string_view const& s1, std::string_view const& s2) { + return std::ranges::equal( + s1, s2, + [](char c1, char c2) { + return std::tolower(static_cast(c1)) == std::tolower(static_cast(c2)); + }); + }(field->name(), key); + }); + return table->column(std::distance(fields.begin(), target)); +} +} // namespace + +void updatePairList(Cache& list, Entry& entry) +{ + auto locate = std::find(list.begin(), list.end(), entry); if (locate == list.end()) { - list.emplace_back(binding, key, enabled); - } else if (!locate->enabled && enabled) { + list.emplace_back(entry); + } else if (!locate->enabled && entry.enabled) { locate->enabled = true; } } std::pair SliceInfoPtr::getSliceFor(int value) const { - int64_t offset = 0; - if (offsets.empty()) { - return {offset, 0}; - } if ((size_t)value >= offsets.size()) { - return {offset, 0}; + return {0, 0}; } return {offsets[value], sizes[value]}; @@ -68,8 +82,6 @@ ArrowTableSlicingCache::ArrowTableSlicingCache(Cache&& bsks, Cache&& bsksUnsorte : bindingsKeys{bsks}, bindingsKeysUnsorted{bsksUnsorted} { - values.resize(bindingsKeys.size()); - counts.resize(bindingsKeys.size()); offsets.resize(bindingsKeys.size()); sizes.resize(bindingsKeys.size()); @@ -81,10 +93,6 @@ void ArrowTableSlicingCache::setCaches(Cache&& bsks, Cache&& bsksUnsorted) { bindingsKeys = bsks; bindingsKeysUnsorted = bsksUnsorted; - values.clear(); - values.resize(bindingsKeys.size()); - counts.clear(); - counts.resize(bindingsKeys.size()); offsets.clear(); offsets.resize(bindingsKeys.size()); sizes.clear(); @@ -97,53 +105,60 @@ void ArrowTableSlicingCache::setCaches(Cache&& bsks, Cache&& bsksUnsorted) arrow::Status ArrowTableSlicingCache::updateCacheEntry(int pos, std::shared_ptr const& table) { - values[pos].reset(); - counts[pos].reset(); offsets[pos].clear(); sizes[pos].clear(); if (table->num_rows() == 0) { return arrow::Status::OK(); } - auto& [b, k, e] = bindingsKeys[pos]; + auto& [b, m, k, e] = bindingsKeys[pos]; if (!e) { throw runtime_error_f("Disabled cache %s/%s update requested", b.c_str(), k.c_str()); } validateOrder(bindingsKeys[pos], table); - arrow::Datum value_counts; - auto options = arrow::compute::ScalarAggregateOptions::Defaults(); - ARROW_ASSIGN_OR_RAISE(value_counts, - arrow::compute::CallFunction("value_counts", {table->GetColumnByName(bindingsKeys[pos].key)}, - &options)); - auto pair = static_cast(value_counts.array()); - values[pos].reset(); - counts[pos].reset(); - values[pos] = std::make_shared>(pair.field(0)->data()); - counts[pos] = std::make_shared>(pair.field(1)->data()); int maxValue = -1; - for (auto i = values[pos]->length() - 1; i >= 0; --i) { - if (values[pos]->Value(i) < 0) { - continue; - } else { - maxValue = values[pos]->Value(i); + auto column = GetColumnByNameCI(table, k); + + // starting from the end, find the first positive value, in a sorted column it is the largest index + for (auto iChunk = column->num_chunks() - 1; iChunk >= 0; --iChunk) { + auto chunk = static_cast>(column->chunk(iChunk)->data()); + for (auto iElement = chunk.length() - 1; iElement >= 0; --iElement) { + auto value = chunk.Value(iElement); + if (value < 0) { + continue; + } else { + maxValue = value; + break; + } + } + if (maxValue >= 0) { break; } } offsets[pos].resize(maxValue + 1); sizes[pos].resize(maxValue + 1); - std::fill(offsets[pos].begin(), offsets[pos].end(), 0); - std::fill(sizes[pos].begin(), sizes[pos].end(), 0); - int64_t offset = 0; - for (auto i = 0U; i < values[pos]->length(); ++i) { - auto value = values[pos]->Value(i); - auto count = counts[pos]->Value(i); - if (value >= 0) { - offsets[pos][value] = offset; - sizes[pos][value] = count; + + // loop over the index and collect size/offset + int lastValue = std::numeric_limits::max(); + int globalRow = 0; + for (auto iChunk = 0; iChunk < column->num_chunks(); ++iChunk) { + auto chunk = static_cast>(column->chunk(iChunk)->data()); + for (auto iElement = 0; iElement < chunk.length(); ++iElement) { + auto v = chunk.Value(iElement); + if (v >= 0) { + if (v == lastValue) { + ++sizes[pos][v]; + } else { + lastValue = v; + ++sizes[pos][v]; + offsets[pos][v] = globalRow; + } + } + ++globalRow; } - offset += count; } + return arrow::Status::OK(); } @@ -154,11 +169,11 @@ arrow::Status ArrowTableSlicingCache::updateCacheEntryUnsorted(int pos, const st if (table->num_rows() == 0) { return arrow::Status::OK(); } - auto& [b, k, e] = bindingsKeysUnsorted[pos]; + auto& [b, m, k, e] = bindingsKeysUnsorted[pos]; if (!e) { throw runtime_error_f("Disabled unsorted cache %s/%s update requested", b.c_str(), k.c_str()); } - auto column = table->GetColumnByName(k); + auto column = GetColumnByNameCI(table, k); auto row = 0; for (auto iChunk = 0; iChunk < column->num_chunks(); ++iChunk) { auto chunk = static_cast>(column->chunk(iChunk)->data()); @@ -195,7 +210,7 @@ std::pair ArrowTableSlicingCache::getCachePos(const Entry& bindingKey int ArrowTableSlicingCache::getCachePosSortedFor(Entry const& bindingKey) const { - auto locate = std::find_if(bindingsKeys.begin(), bindingsKeys.end(), [&](Entry const& bk) { return (bindingKey.binding == bk.binding) && (bindingKey.key == bk.key); }); + auto locate = std::ranges::find(bindingsKeys, bindingKey); if (locate != bindingsKeys.end()) { return std::distance(bindingsKeys.begin(), locate); } @@ -204,7 +219,7 @@ int ArrowTableSlicingCache::getCachePosSortedFor(Entry const& bindingKey) const int ArrowTableSlicingCache::getCachePosUnsortedFor(Entry const& bindingKey) const { - auto locate_unsorted = std::find_if(bindingsKeysUnsorted.begin(), bindingsKeysUnsorted.end(), [&](Entry const& bk) { return (bindingKey.binding == bk.binding) && (bindingKey.key == bk.key); }); + auto locate_unsorted = std::ranges::find(bindingsKeysUnsorted, bindingKey); if (locate_unsorted != bindingsKeysUnsorted.end()) { return std::distance(bindingsKeysUnsorted.begin(), locate_unsorted); } @@ -238,13 +253,6 @@ SliceInfoUnsortedPtr ArrowTableSlicingCache::getCacheUnsortedFor(const Entry& bi SliceInfoPtr ArrowTableSlicingCache::getCacheForPos(int pos) const { - if (values[pos] == nullptr && counts[pos] == nullptr) { - return { - {}, // - {} // - }; - } - return { gsl::span{offsets[pos].data(), offsets[pos].size()}, // gsl::span(sizes[pos].data(), sizes[pos].size()) // @@ -261,10 +269,13 @@ SliceInfoUnsortedPtr ArrowTableSlicingCache::getCacheUnsortedForPos(int pos) con void ArrowTableSlicingCache::validateOrder(Entry const& bindingKey, const std::shared_ptr& input) { - auto const& [target, key, enabled] = bindingKey; - auto column = input->GetColumnByName(key); + auto const& [target, matcher, key, enabled] = bindingKey; + if (!enabled) { + return; + } + auto column = o2::framework::GetColumnByNameCI(input, key); auto array0 = static_cast>(column->chunk(0)->data()); - int32_t prev = 0; + int32_t prev; int32_t cur = array0.Value(0); int32_t lastNeg = cur < 0 ? cur : 0; int32_t lastPos = cur < 0 ? -1 : cur; diff --git a/Framework/Core/src/CallbacksPolicy.cxx b/Framework/Core/src/CallbacksPolicy.cxx index aa22fa830c4c2..5e7bd7bc8306a 100644 --- a/Framework/Core/src/CallbacksPolicy.cxx +++ b/Framework/Core/src/CallbacksPolicy.cxx @@ -23,7 +23,7 @@ namespace o2::framework { -static bool checkPrescale(const TimingInfo& info, int prescale, bool startProcessing) +static bool checkPrescale(const TimingInfo& info, int prescale, bool startProcessing, bool noDownscaling) { if (prescale <= 1) { static size_t counter = 0; @@ -31,7 +31,7 @@ static bool checkPrescale(const TimingInfo& info, int prescale, bool startProces if (startProcessing) { counter++; } - if (counter <= 100000) { + if (counter <= 100000 || noDownscaling) { return true; } if (counter > 100000 * downscaleFactor) { @@ -53,6 +53,8 @@ CallbacksPolicy epnProcessReporting() if (!prescale) { prescale = 1; } + static bool noDownscaling = getenv("DPL_REPORT_PROCESSING_NO_DOWNSCALING") != nullptr && std::abs(atoi(getenv("DPL_REPORT_PROCESSING_NO_DOWNSCALING"))); + return { .matcher = [forceReport](DeviceSpec const&, ConfigContext const& context) -> bool { static bool report = DefaultsHelpers::deploymentMode() == DeploymentMode::OnlineDDS || forceReport; @@ -61,7 +63,7 @@ CallbacksPolicy epnProcessReporting() .policy = [prescale](CallbackService& callbacks, InitContext& context) -> void { callbacks.set([prescale](ServiceRegistryRef registry, int op) { auto& info = registry.get(); - if ((int)info.firstTForbit != -1 && checkPrescale(info, prescale, true)) { + if ((int)info.firstTForbit != -1 && checkPrescale(info, prescale, true, noDownscaling)) { char const* what = info.isTimer() ? "timer" : "timeslice"; LOGP(info, "Processing {}:{}, tfCounter:{}, firstTForbit:{}, runNumber:{}, creation:{}, action:{}", what, info.timeslice, info.tfCounter, info.firstTForbit, info.runNumber, info.creation, op); @@ -70,7 +72,7 @@ CallbacksPolicy epnProcessReporting() }); callbacks.set([prescale](ServiceRegistryRef registry, int op) { auto& info = registry.get(); - if ((int)info.firstTForbit != -1 && checkPrescale(info, prescale, false)) { + if ((int)info.firstTForbit != -1 && checkPrescale(info, prescale, false, noDownscaling)) { char const* what = info.isTimer() ? "timer" : "timeslice"; LOGP(info, "Done processing {}:{}, tfCounter:{}, firstTForbit:{}, runNumber:{}, creation:{}, action:{}, wall:{}", what, info.timeslice, info.tfCounter, info.firstTForbit, info.runNumber, info.creation, op, uv_hrtime() - info.lapse); diff --git a/Framework/Core/src/ChannelSpecHelpers.cxx b/Framework/Core/src/ChannelSpecHelpers.cxx index c66a1964c12a0..0578c51403b26 100644 --- a/Framework/Core/src/ChannelSpecHelpers.cxx +++ b/Framework/Core/src/ChannelSpecHelpers.cxx @@ -339,6 +339,10 @@ std::string ChannelSpecHelpers::defaultIPCFolder() if (channelPrefix) { return fmt::format("@dpl_{}_", channelPrefix); } + channelPrefix = getenv("SLURM_JOB_ID"); + if (channelPrefix) { + return fmt::format("@dpl_{}_", channelPrefix); + } return "@"; #else /// Find out a place where we can write the sockets diff --git a/Framework/Core/src/CommonDataProcessors.cxx b/Framework/Core/src/CommonDataProcessors.cxx index c2431b3ab068d..67c6314de1c34 100644 --- a/Framework/Core/src/CommonDataProcessors.cxx +++ b/Framework/Core/src/CommonDataProcessors.cxx @@ -44,6 +44,8 @@ using namespace o2::framework::data_matcher; // Special log to track callbacks we know about O2_DECLARE_DYNAMIC_LOG(callbacks); +O2_DECLARE_DYNAMIC_LOG(rate_limiting); +O2_DECLARE_DYNAMIC_LOG(quota); namespace o2::framework { @@ -211,6 +213,8 @@ DataProcessorSpec CommonDataProcessors::getDummySink(std::vector cons auto oldestPossingTimeslice = timesliceIndex.getOldestPossibleOutput().timeslice.value; auto& stats = services.get(); stats.updateStats({(int)ProcessingStatsId::CONSUMED_TIMEFRAMES, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.updateStats({(int)ProcessingStatsId::TIMESLICE_NUMBER_DONE, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.processCommandQueue(); }; callbacks.set(domainInfoUpdated); @@ -224,17 +228,90 @@ DataProcessorSpec CommonDataProcessors::getDummySink(std::vector cons .labels = {{"resilient"}}}; } +// For the cases were the driver is guaranteed to be there (e.g. in analysis) we can use a +// more sophisticated controller which can get offers for timeslices so that we can rate limit +// across multiple input devices and rate limit shared memory usage without race conditions +DataProcessorSpec CommonDataProcessors::getScheduledDummySink(std::vector const& danglingOutputInputs) +{ + return DataProcessorSpec{ + .name = "internal-dpl-injected-dummy-sink", + .inputs = danglingOutputInputs, + .algorithm = AlgorithmSpec{adaptStateful([](CallbackService& callbacks, DeviceState& deviceState, InitContext& ic) { + // We update the number of consumed timeframes based on the oldestPossingTimeslice + // this information will be aggregated in the driver which will then decide wether or not a new offer for + // a timeslice should be done and to which device + auto domainInfoUpdated = [](ServiceRegistryRef services, size_t timeslice, ChannelIndex channelIndex) { + LOGP(debug, "Domain info updated with timeslice {}", timeslice); + auto& timesliceIndex = services.get(); + auto oldestPossingTimeslice = timesliceIndex.getOldestPossibleOutput().timeslice.value; + auto& stats = services.get(); + O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "run", "Consumed timeframes (domain info updated) to be set to %zu.", oldestPossingTimeslice); + stats.updateStats({(int)ProcessingStatsId::CONSUMED_TIMEFRAMES, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.updateStats({(int)ProcessingStatsId::TIMESLICE_NUMBER_DONE, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.processCommandQueue(); + }; + callbacks.set(domainInfoUpdated); + + return adaptStateless([](DataProcessingStats& stats, TimesliceIndex& timesliceIndex) { + O2_SIGNPOST_ID_GENERATE(sid, rate_limiting); + auto oldestPossingTimeslice = timesliceIndex.getOldestPossibleOutput().timeslice.value; + O2_SIGNPOST_EVENT_EMIT(rate_limiting, sid, "run", "Consumed timeframes (processing) to be set to %zu.", oldestPossingTimeslice); + stats.updateStats({(int)ProcessingStatsId::CONSUMED_TIMEFRAMES, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.updateStats({(int)ProcessingStatsId::TIMESLICE_NUMBER_DONE, DataProcessingStats::Op::Set, (int64_t)oldestPossingTimeslice}); + stats.processCommandQueue(); + }); + })}, + .labels = {{"resilient"}}}; +} + AlgorithmSpec CommonDataProcessors::wrapWithRateLimiting(AlgorithmSpec spec) { return PluginManager::wrapAlgorithm(spec, [](AlgorithmSpec::ProcessCallback& original, ProcessingContext& pcx) -> void { auto& raw = pcx.services().get(); static RateLimiter limiter; + O2_SIGNPOST_ID_FROM_POINTER(sid, rate_limiting, &pcx); auto limit = std::stoi(raw.device()->fConfig->GetValue("timeframes-rate-limit")); - LOG(detail) << "Rate limiting to " << limit << " timeframes in flight"; + O2_SIGNPOST_EVENT_EMIT_DETAIL(rate_limiting, sid, "rate limiting callback", + "Rate limiting to %d timeframes in flight", limit); limiter.check(pcx, limit, 2000); - LOG(detail) << "Rate limiting passed. Invoking old callback"; + O2_SIGNPOST_EVENT_EMIT_DETAIL(rate_limiting, sid, "rate limiting callback", + "Rate limiting passed. Invoking old callback."); + original(pcx); + O2_SIGNPOST_EVENT_EMIT_DETAIL(rate_limiting, sid, "rate limiting callback", + "Rate limited callback done."); + }); +} + +// The wrapped algorithm consumes 1 timeslice every time is invoked +AlgorithmSpec CommonDataProcessors::wrapWithTimesliceConsumption(AlgorithmSpec spec) +{ + return PluginManager::wrapAlgorithm(spec, [](AlgorithmSpec::ProcessCallback& original, ProcessingContext& pcx) -> void { original(pcx); - LOG(detail) << "Rate limited callback done"; + + auto disposeResources = [](int taskId, + std::array& offers, + ComputingQuotaStats& stats, + std::function accountDisposed) { + ComputingQuotaOffer disposed; + disposed.sharedMemory = 0; + // When invoked, we have processed one timeslice by construction. + int64_t timeslicesProcessed = 1; + for (auto& offer : offers) { + if (offer.user != taskId) { + continue; + } + int64_t toRemove = std::min((int64_t)timeslicesProcessed, offer.timeslices); + offer.timeslices -= toRemove; + timeslicesProcessed -= toRemove; + disposed.timeslices += toRemove; + if (timeslicesProcessed <= 0) { + break; + } + } + return accountDisposed(disposed, stats); + }; + pcx.services().get().offerConsumers.emplace_back(disposeResources); }); } diff --git a/Framework/Core/src/CommonLabels.cxx b/Framework/Core/src/CommonLabels.cxx new file mode 100644 index 0000000000000..f728e194f611b --- /dev/null +++ b/Framework/Core/src/CommonLabels.cxx @@ -0,0 +1,19 @@ +// 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/CommonLabels.h" + +namespace o2::framework +{ + +const DataProcessorLabel suppressDomainInfoLabel = {"suppress-domain-info"}; + +} // namespace o2::framework diff --git a/Framework/Core/src/CommonMessageBackends.cxx b/Framework/Core/src/CommonMessageBackends.cxx index 79bd84307df15..25bf6a138dee4 100644 --- a/Framework/Core/src/CommonMessageBackends.cxx +++ b/Framework/Core/src/CommonMessageBackends.cxx @@ -57,7 +57,18 @@ o2::framework::ServiceSpec CommonMessageBackends::fairMQDeviceProxy() /// some of the channels are added only later on to the party, /// (e.g. by ECS) and Init might not be late enough to /// account for them. - proxy->bind(outputs, inputs, forwards, *device); }, + std::function bindByName = [device](std::string const& channelName) -> fair::mq::Channel& { + auto channel = device->GetChannels().find(channelName); + if (channel == device->GetChannels().end()) { + LOGP(fatal, "Expected channel {} not configured.", channelName); + } + return channel->second.at(0); + }; + + std::function newStateCallback = [device]() -> bool { + return device->NewStatePending(); + }; + proxy->bind(outputs, inputs, forwards, bindByName, newStateCallback); }, }; } diff --git a/Framework/Core/src/CommonServices.cxx b/Framework/Core/src/CommonServices.cxx index 22324cd84b390..2cdd046dedc34 100644 --- a/Framework/Core/src/CommonServices.cxx +++ b/Framework/Core/src/CommonServices.cxx @@ -45,6 +45,7 @@ #include "Framework/DefaultsHelpers.h" #include "Framework/Signpost.h" #include "Framework/DriverConfig.h" +#include "Framework/CommonLabels.h" #include "TextDriverClient.h" #include "WSDriverClient.h" @@ -413,11 +414,13 @@ o2::framework::ServiceSpec CommonServices::dataRelayer() .name = "datarelayer", .init = [](ServiceRegistryRef services, DeviceState&, fair::mq::ProgOptions& options) -> ServiceHandle { auto& spec = services.get(); + int pipelineLength = DefaultsHelpers::pipelineLength(options); return ServiceHandle{TypeIdHelpers::uniqueId(), new DataRelayer(spec.completionPolicy, spec.inputs, services.get(), - services)}; + services, + pipelineLength)}; }, .configure = noConfiguration(), .kind = ServiceKind::Serial}; @@ -586,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 @@ -604,6 +647,12 @@ o2::framework::ServiceSpec break; } } + for (const auto& label : services.get().labels) { + if (label == suppressDomainInfoLabel) { + decongestion->suppressDomainInfo = true; + break; + } + } auto& queue = services.get(); decongestion->oldestPossibleTimesliceTask = AsyncQueueHelpers::create(queue, {.name = "oldest-possible-timeslice", .score = 100}); return ServiceHandle{TypeIdHelpers::uniqueId(), decongestion, ServiceKind::Serial}; @@ -696,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; @@ -826,30 +891,34 @@ auto flushMetrics(ServiceRegistryRef registry, DataProcessingStats& stats) -> vo auto& relayer = registry.get(); // Send all the relevant metrics for the relayer to update the GUI - stats.flushChangedMetrics([&monitoring](DataProcessingStats::MetricSpec const& spec, int64_t timestamp, int64_t value) mutable -> void { + stats.flushChangedMetrics([&monitoring, sid](DataProcessingStats::MetricSpec const& spec, int64_t timestamp, int64_t value) mutable -> void { // convert timestamp to a time_point auto tp = std::chrono::time_point(std::chrono::milliseconds(timestamp)); auto metric = o2::monitoring::Metric{spec.name, Metric::DefaultVerbosity, tp}; if (spec.kind == DataProcessingStats::Kind::UInt64) { if (value < 0) { - LOG(debug) << "Value for " << spec.name << " is negative, setting to 0"; + O2_SIGNPOST_EVENT_EMIT(monitoring_service, sid, "flushChangedMetrics", "Value for %{public}s is negative, setting to 0", + spec.name.c_str()); value = 0; } metric.addValue((uint64_t)value, "value"); } else { if (value > (int64_t)std::numeric_limits::max()) { - LOG(warning) << "Value for " << spec.name << " is too large, setting to INT_MAX"; + O2_SIGNPOST_EVENT_EMIT(monitoring_service, sid, "flushChangedMetrics", "Value for %{public}s is too large, setting to INT_MAX", + spec.name.c_str()); value = (int64_t)std::numeric_limits::max(); } if (value < (int64_t)std::numeric_limits::min()) { + O2_SIGNPOST_EVENT_EMIT(monitoring_service, sid, "flushChangedMetrics", "Value for %{public}s is too small, setting to INT_MIN", + spec.name.c_str()); value = (int64_t)std::numeric_limits::min(); - LOG(warning) << "Value for " << spec.name << " is too small, setting to INT_MIN"; } metric.addValue((int)value, "value"); } if (spec.scope == DataProcessingStats::Scope::DPL) { metric.addTag(o2::monitoring::tags::Key::Subsystem, o2::monitoring::tags::Value::DPL); } + O2_SIGNPOST_EVENT_EMIT(monitoring_service, sid, "flushChangedMetrics", "Flushing metric %{public}s", spec.name.c_str()); monitoring.send(std::move(metric)); }); relayer.sendContextState(); @@ -1076,6 +1145,38 @@ o2::framework::ServiceSpec CommonServices::dataProcessingStats() .minPublishInterval = 0, .maxRefreshLatency = 10000, .sendInitialValue = true}, + MetricSpec{.name = "timeslice-offer-number-consumed", + .enabled = arrowAndResourceLimitingMetrics, + .metricId = static_cast(ProcessingStatsId::TIMESLICE_OFFER_NUMBER_CONSUMED), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 0, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "timeslices-expired", + .enabled = arrowAndResourceLimitingMetrics, + .metricId = static_cast(ProcessingStatsId::TIMESLICE_NUMBER_EXPIRED), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 0, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "timeslices-started", + .enabled = arrowAndResourceLimitingMetrics, + .metricId = static_cast(ProcessingStatsId::TIMESLICE_NUMBER_STARTED), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 0, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "timeslices-done", + .enabled = arrowAndResourceLimitingMetrics, + .metricId = static_cast(ProcessingStatsId::TIMESLICE_NUMBER_DONE), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 0, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, MetricSpec{.name = "resources-missing", .enabled = enableDebugMetrics, .metricId = static_cast(ProcessingStatsId::RESOURCES_MISSING), @@ -1107,6 +1208,46 @@ o2::framework::ServiceSpec CommonServices::dataProcessingStats() .scope = Scope::DPL, .minPublishInterval = 0, .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "ccdb-cache-hit", + .enabled = true, + .metricId = static_cast(ProcessingStatsId::CCDB_CACHE_HIT), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 1000, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "ccdb-cache-miss", + .enabled = true, + .metricId = static_cast(ProcessingStatsId::CCDB_CACHE_MISS), + .kind = Kind::UInt64, + .scope = Scope::DPL, + .minPublishInterval = 1000, + .maxRefreshLatency = 10000, + .sendInitialValue = true}, + MetricSpec{.name = "ccdb-cache-failure", + .enabled = true, + .metricId = static_cast(ProcessingStatsId::CCDB_CACHE_FAILURE), + .kind = Kind::UInt64, + .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) { @@ -1233,17 +1374,6 @@ o2::framework::ServiceSpec CommonServices::dataProcessorContextSpec() .kind = ServiceKind::Serial}; } -o2::framework::ServiceSpec CommonServices::deviceContextSpec() -{ - return ServiceSpec{ - .name = "device-context", - .init = [](ServiceRegistryRef, DeviceState&, fair::mq::ProgOptions&) -> ServiceHandle { - return ServiceHandle{TypeIdHelpers::uniqueId(), new DeviceContext()}; - }, - .configure = noConfiguration(), - .kind = ServiceKind::Serial}; -} - o2::framework::ServiceSpec CommonServices::dataAllocatorSpec() { return ServiceSpec{ diff --git a/Framework/Core/src/CompletionPolicy.cxx b/Framework/Core/src/CompletionPolicy.cxx index ec8997e32c5db..a09028b9249f3 100644 --- a/Framework/Core/src/CompletionPolicy.cxx +++ b/Framework/Core/src/CompletionPolicy.cxx @@ -26,11 +26,7 @@ std::vector { return { CompletionPolicyHelpers::consumeWhenAllOrdered("internal-dpl-aod-writer"), -#if __has_include() CompletionPolicyHelpers::consumeWhenAnyZeroCount("internal-dpl-injected-dummy-sink", [](DeviceSpec const& s) { return s.name.find("internal-dpl-injected-dummy-sink") != std::string::npos; }), -#else - CompletionPolicyHelpers::consumeWhenAny("internal-dpl-injected-dummy-sink", [](DeviceSpec const& s) { return s.name.find("internal-dpl-injected-dummy-sink") != std::string::npos; }), -#endif CompletionPolicyHelpers::consumeWhenAll()}; } diff --git a/Framework/Core/src/CompletionPolicyHelpers.cxx b/Framework/Core/src/CompletionPolicyHelpers.cxx index e682f9a7c7dd6..e5a91ae58f899 100644 --- a/Framework/Core/src/CompletionPolicyHelpers.cxx +++ b/Framework/Core/src/CompletionPolicyHelpers.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. // @@ -11,6 +11,7 @@ #include "Framework/CompletionPolicyHelpers.h" #include "Framework/CompletionPolicy.h" +#include "Framework/DataProcessingHeader.h" #include "Framework/InputSpan.h" #include "Framework/DeviceSpec.h" #include "Framework/CompilerBuiltins.h" @@ -19,9 +20,7 @@ #include "Framework/TimingInfo.h" #include "DecongestionService.h" #include "Framework/Signpost.h" -#if __has_include() #include -#endif #include #include @@ -252,7 +251,6 @@ CompletionPolicy CompletionPolicyHelpers::consumeExistingWhenAny(const char* nam }}; } -#if __has_include() CompletionPolicy CompletionPolicyHelpers::consumeWhenAnyZeroCount(const char* name, CompletionPolicy::Matcher matcher) { auto callback = [](InputSpan const& inputs, std::vector const&, ServiceRegistryRef& ref) -> CompletionPolicy::CompletionOp { @@ -265,7 +263,34 @@ CompletionPolicy CompletionPolicyHelpers::consumeWhenAnyZeroCount(const char* na }; return CompletionPolicy{name, matcher, callback, false}; } -#endif + +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) { + continue; + } + o2::framework::DataProcessingHeader const* dph = o2::header::get(input.header); + if (dph && !TimingInfo::timesliceIsTimer(dph->startTime)) { + currentTimeslice = dph->startTime; + break; + } + } + + auto& timesliceIndex = ref.get(); + auto oldestPossibleTimeslice = timesliceIndex.getOldestPossibleInput().timeslice.value; + + if (currentTimeslice >= oldestPossibleTimeslice) { + return CompletionPolicy::CompletionOp::Retry; + } + return CompletionPolicy::CompletionOp::Consume; + }; + return CompletionPolicy{name, matcher, callback, false}; +} CompletionPolicy CompletionPolicyHelpers::consumeWhenAny(const char* name, CompletionPolicy::Matcher matcher) { @@ -302,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 56b7f4a59be88..5dd4249cab519 100644 --- a/Framework/Core/src/ComputingQuotaEvaluator.cxx +++ b/Framework/Core/src/ComputingQuotaEvaluator.cxx @@ -13,17 +13,17 @@ #include "Framework/DataProcessingStats.h" #include "Framework/ServiceRegistryRef.h" #include "Framework/DeviceState.h" -#include "Framework/DriverClient.h" -#include "Framework/Monitoring.h" -#include "Framework/Logger.h" +#include "Framework/Signpost.h" #include #include #include #include +#include +#include +#include -#define LOGLEVEL debug - +O2_DECLARE_DYNAMIC_LOG(quota); namespace o2::framework { @@ -36,14 +36,14 @@ ComputingQuotaEvaluator::ComputingQuotaEvaluator(ServiceRegistryRef ref) // so this will only work with some device which does not require // any CPU. Notice this will have troubles if a given DPL process // runs for more than a year. - mOffers[0] = { - 0, - 0, - 0, - -1, - -1, - OfferScore::Unneeded, - true}; + mOffers[0] = ComputingQuotaOffer{ + .cpu = 0, + .memory = 0, + .sharedMemory = 0, + .timeslices = 0, + .runtime = -1, + .score = OfferScore::Unneeded, + .valid = true}; mInfos[0] = { uv_now(state.loop), 0, @@ -62,8 +62,10 @@ 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); + auto selectOffer = [&offers = this->mOffers, &infos = this->mInfos, task](int ref, uint64_t now) { auto& selected = offers[ref]; auto& info = infos[ref]; @@ -89,28 +91,39 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& // LOG(LOGLEVEL) << "No particular resource was requested, so we schedule task anyways"; return enough; } + O2_SIGNPOST_ID_GENERATE(sid, quota); if (enough) { - LOGP(LOGLEVEL, "{} offers were selected for a total of: cpu {}, memory {}, shared memory {}", result.size(), totalOffer.cpu, totalOffer.memory, totalOffer.sharedMemory); - //LOG(LOGLEVEL) << " The following offers were selected for computation: {} " << fmt::join(result, ", "); + O2_SIGNPOST_START(quota, sid, "summary", "%zu offers were selected for a total of: cpu %d, memory %lli, shared memory %lli", + result.size(), totalOffer.cpu, totalOffer.memory, totalOffer.sharedMemory); + for (auto& offer : result) { + // We pretend each offer id is a pointer, to have a unique id. + O2_SIGNPOST_ID_FROM_POINTER(oid, quota, (void*)(int64_t)(offer * 8)); + O2_SIGNPOST_START(quota, oid, "offers", "Offer %d has been selected.", offer); + } dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCES_SATISFACTORY), DataProcessingStats::Op::Add, 1}); } else { - 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()) { - // LOGP(LOGLEVEL, " The following offers were invalid: {}", fmt::join(stats.invalidOffers, ", ")); + O2_SIGNPOST_EVENT_EMIT(quota, sid, "summary", "The following offers were invalid: %s", fmt::format("{}", fmt::join(stats.invalidOffers, ", ")).c_str()); } if (stats.otherUser.size()) { - // LOGP(LOGLEVEL, " The following offers were owned by other users: {}", fmt::join(stats.otherUser, ", ")); + O2_SIGNPOST_EVENT_EMIT(quota, sid, "summary", "The following offers were owned by other users: %s", fmt::format("{}", fmt::join(stats.otherUser, ", ")).c_str()); } if (stats.expired.size()) { - // LOGP(LOGLEVEL, " The following offers are expired: {}", fmt::join(stats.expired, ", ")); + O2_SIGNPOST_EVENT_EMIT(quota, sid, "summary", "The following offers are expired: %s", fmt::format("{}", fmt::join(stats.expired, ", ")).c_str()); } if (stats.unexpiring.size() > 1) { - // LOGP(LOGLEVEL, " The following offers will never expire: {}", fmt::join(stats.unexpiring, ", ")); + O2_SIGNPOST_EVENT_EMIT(quota, sid, "summary", "The following offers will never expire: %s", fmt::format("{}", fmt::join(stats.unexpiring, ", ")).c_str()); } + O2_SIGNPOST_END(quota, sid, "summary", "Done selecting offers."); return enough; }; @@ -122,6 +135,7 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& auto& offer = mOffers[i]; auto& info = mInfos[i]; if (enough) { + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "We have enough offers. We can continue for computation."); break; } // Ignore: @@ -129,26 +143,30 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& // - Offers which belong to another task // - Expired offers if (offer.valid == false) { + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d is not valid. Skipping", i); stats.invalidOffers.push_back(i); continue; } if (offer.user != -1 && offer.user != task) { + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d already offered to some other user", i); stats.otherUser.push_back(i); continue; } if (offer.runtime < 0) { stats.unexpiring.push_back(i); } else if (offer.runtime + info.received < now) { - LOGP(LOGLEVEL, "Offer {} expired since {} milliseconds and holds {}MB", i, now - offer.runtime - info.received, offer.sharedMemory / 1000000); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d expired since %llu milliseconds and holds %llu MB and %llu timeslices", + i, now - offer.runtime - info.received, offer.sharedMemory / 1000000, offer.timeslices); mExpiredOffers.push_back(ComputingQuotaOfferRef{i}); stats.expired.push_back(i); continue; } else { - LOGP(LOGLEVEL, "Offer {} still valid for {} milliseconds, providing {}MB", i, offer.runtime + info.received - now, offer.sharedMemory / 1000000); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d still valid for %llu milliseconds, providing %llu MB and %llu timeslices", + i, offer.runtime + info.received - now, offer.sharedMemory / 1000000, offer.timeslices); if (minValidity == 0) { minValidity = offer.runtime + info.received - now; } - minValidity = std::min(minValidity,(int64_t)(offer.runtime + info.received - now)); + minValidity = std::min(minValidity, (int64_t)(offer.runtime + info.received - now)); } /// We then check if the offer is suitable assert(offer.sharedMemory >= 0); @@ -156,35 +174,45 @@ bool ComputingQuotaEvaluator::selectOffer(int task, ComputingQuotaRequest const& tmp.cpu += offer.cpu; tmp.memory += offer.memory; tmp.sharedMemory += offer.sharedMemory; - offer.score = selector(offer, tmp); + tmp.timeslices += offer.timeslices; + offer.score = selector(offer, accumulated); switch (offer.score) { case OfferScore::Unneeded: + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d considered not needed. Skipping", i); continue; case OfferScore::Unsuitable: + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d considered Unsuitable. Skipping", i); continue; case OfferScore::More: selectOffer(i, now); accumulated = tmp; stats.selectedOffers.push_back(i); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Offer %d selected but not enough. %llu MB, %d cores and %llu timeslices are not enough.", + i, tmp.sharedMemory / 1000000, tmp.cpu, tmp.timeslices); continue; case OfferScore::Enough: selectOffer(i, now); accumulated = tmp; stats.selectedOffers.push_back(i); enough = true; + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Selected %zu offers providing %llu MB, %d cores and %llu timeslices are deemed enough.", + stats.selectedOffers.size(), tmp.sharedMemory / 1000000, tmp.cpu, tmp.timeslices); break; }; } if (minValidity != 0) { - LOGP(LOGLEVEL, "Next offer to expire in {} milliseconds", minValidity); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "select", "Next offer to expire in %llu milliseconds", minValidity); uv_timer_start(mTimer, [](uv_timer_t* handle) { - LOGP(LOGLEVEL, "Offer should be expired by now, checking again"); - }, - minValidity + 100, 0); + O2_SIGNPOST_ID_GENERATE(tid, quota); + 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) @@ -213,6 +241,8 @@ void ComputingQuotaEvaluator::dispose(int taskId) continue; } if (offer.sharedMemory <= 0) { + O2_SIGNPOST_ID_FROM_POINTER(oid, quota, (void*)(int64_t)(oi * 8)); + O2_SIGNPOST_END(quota, oid, "offers", "Offer %d back to not needed.", oi); offer.valid = false; offer.score = OfferScore::Unneeded; } @@ -222,55 +252,85 @@ void ComputingQuotaEvaluator::dispose(int taskId) /// Move offers from the pending list to the actual available offers void ComputingQuotaEvaluator::updateOffers(std::vector& pending, uint64_t now) { + O2_SIGNPOST_ID_GENERATE(oid, quota); + O2_SIGNPOST_START(quota, oid, "updateOffers", "Starting to process %zu received offers", pending.size()); + int lastValid = -1; for (size_t oi = 0; oi < mOffers.size(); oi++) { auto& storeOffer = mOffers[oi]; auto& info = mInfos[oi]; if (pending.empty()) { + O2_SIGNPOST_END(quota, oid, "updateOffers", "No more pending offers to process"); return; } if (storeOffer.valid == true) { + O2_SIGNPOST_EVENT_EMIT(quota, oid, "updateOffers", "Skipping update of offer %zu because it's still valid", oi); + // In general we want to fill an invalid offer. If we do not find any + // we add to the last valid offer we found. + lastValid = oi; continue; } info.received = now; auto& offer = pending.back(); + O2_SIGNPOST_EVENT_EMIT(quota, oid, "updateOffers", "Updating of offer %zu at %llu. Cpu: %d, Shared Memory %lli, Timeslices: %lli", + oi, now, offer.cpu, offer.sharedMemory, offer.timeslices); storeOffer = offer; storeOffer.valid = true; pending.pop_back(); } + if (lastValid == -1) { + O2_SIGNPOST_END_WITH_ERROR(quota, oid, "updateOffers", "ComputingQuotaOffer losts. This should never happen."); + return; + } + auto& lastValidOffer = mOffers[lastValid]; + for (auto& stillPending : pending) { + lastValidOffer.cpu += stillPending.cpu; + lastValidOffer.memory += stillPending.memory; + lastValidOffer.sharedMemory += stillPending.sharedMemory; + lastValidOffer.timeslices += stillPending.timeslices; + lastValidOffer.runtime = std::max(lastValidOffer.runtime, stillPending.runtime); + } + pending.clear(); + auto& updatedOffer = mOffers[lastValid]; + O2_SIGNPOST_END(quota, oid, "updateOffers", "Remaining offers cohalesced to %d. New values: Cpu%d, Shared Memory %lli, Timeslices %lli", + lastValid, updatedOffer.cpu, updatedOffer.sharedMemory, updatedOffer.timeslices); } void ComputingQuotaEvaluator::handleExpired(std::function expirator) { static int nothingToDoCount = mExpiredOffers.size(); + O2_SIGNPOST_ID_GENERATE(qid, quota); if (mExpiredOffers.size()) { - LOGP(LOGLEVEL, "Handling {} expired offers", mExpiredOffers.size()); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "handleExpired", "Handling %zu expired offers", mExpiredOffers.size()); nothingToDoCount = 0; } else { if (nothingToDoCount == 0) { nothingToDoCount++; - LOGP(LOGLEVEL, "No expired offers"); + O2_SIGNPOST_EVENT_EMIT(quota, qid, "handleExpired", "No expired offers"); } } /// Whenever an offer is expired, we give back the resources /// to the driver. for (auto& ref : mExpiredOffers) { auto& offer = mOffers[ref.index]; - if (offer.sharedMemory < 0) { - LOGP(LOGLEVEL, "Offer {} does not have any more memory. Marking it as invalid.", ref.index); + O2_SIGNPOST_ID_FROM_POINTER(oid, quota, (void*)(int64_t)(ref.index * 8)); + if (offer.sharedMemory < 0 && offer.timeslices < 0) { + O2_SIGNPOST_END(quota, oid, "handleExpired", "Offer %d does not have any more resources. Marking it as invalid.", ref.index); offer.valid = false; offer.score = OfferScore::Unneeded; continue; } // FIXME: offers should go through the driver client, not the monitoring // api. - LOGP(LOGLEVEL, "Offer {} expired. Giving back {}MB and {} cores", ref.index, offer.sharedMemory / 1000000, offer.cpu); - assert(offer.sharedMemory >= 0); - mStats.totalExpiredBytes += offer.sharedMemory; + O2_SIGNPOST_END(quota, oid, "handleExpired", "Offer %d expired. Giving back %llu MB, %d cores and %llu timeslices", + ref.index, offer.sharedMemory / 1000000, offer.cpu, offer.timeslices); + mStats.totalExpiredBytes += std::max(offer.sharedMemory, 0); + mStats.totalExpiredTimeslices += std::max(offer.timeslices, 0); mStats.totalExpiredOffers++; expirator(offer, mStats); - //driverClient.tell("expired shmem {}", offer.sharedMemory); - //driverClient.tell("expired cpu {}", offer.cpu); + // driverClient.tell("expired shmem {}", offer.sharedMemory); + // driverClient.tell("expired cpu {}", offer.cpu); offer.sharedMemory = -1; + offer.timeslices = -1; offer.valid = false; offer.score = OfferScore::Unneeded; } 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 a39e98c6f5310..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) @@ -276,7 +278,7 @@ void WSDPLHandler::endHeaders() } /// Create an appropriate reply LOG(debug) << "Got upgrade request with nonce " << mHeaders["sec-websocket-key"].c_str(); - std::string reply = encode_websocket_handshake_reply(mHeaders["sec-websocket-key"].c_str()); + std::string reply = encode_websocket_handshake_reply(mHeaders["sec-websocket-key"].c_str(), "dpl"); mHandshaken = true; uv_buf_t bfr = uv_buf_init(strdup(reply.data()), reply.size()); @@ -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 4b559ef26191e..d7bfff0dbf19d 100644 --- a/Framework/Core/src/DataAllocator.cxx +++ b/Framework/Core/src/DataAllocator.cxx @@ -11,7 +11,6 @@ #include "Framework/CompilerBuiltins.h" #include "Framework/Lifetime.h" #include "Framework/TableBuilder.h" -#include "Framework/TableTreeHelpers.h" #include "Framework/DataAllocator.h" #include "Framework/MessageContext.h" #include "Framework/ArrowContext.h" @@ -19,6 +18,7 @@ #include "Framework/DataProcessingHeader.h" #include "Framework/FairMQResizableBuffer.h" #include "Framework/DataProcessingContext.h" +#include "Framework/FragmentToBatch.h" #include "Framework/DeviceSpec.h" #include "Framework/StreamContext.h" #include "Framework/Signpost.h" @@ -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 a902ed9326e07..b45a48c28f691 100644 --- a/Framework/Core/src/DataProcessingDevice.cxx +++ b/Framework/Core/src/DataProcessingDevice.cxx @@ -10,6 +10,7 @@ // or submit itself to any jurisdiction. #include "Framework/AsyncQueue.h" #include "Framework/DataProcessingDevice.h" +#include #include "Framework/ControlService.h" #include "Framework/ComputingQuotaEvaluator.h" #include "Framework/DataProcessingHeader.h" @@ -17,6 +18,7 @@ #include "Framework/DataProcessor.h" #include "Framework/DataSpecUtils.h" #include "Framework/DeviceState.h" +#include "Framework/DeviceStateEnums.h" #include "Framework/DispatchPolicy.h" #include "Framework/DispatchControl.h" #include "Framework/DanglingContext.h" @@ -48,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" @@ -57,9 +60,7 @@ #include #include #include -#if __has_include() #include -#endif #include #include #include @@ -96,6 +97,10 @@ O2_DECLARE_DYNAMIC_LOG(calibration); O2_DECLARE_DYNAMIC_LOG(async_queue); // Special log to track the forwarding requests O2_DECLARE_DYNAMIC_LOG(forwarding); +// Special log to track CCDB related requests +O2_DECLARE_DYNAMIC_LOG(ccdb); +// Special log to track task scheduling +O2_DECLARE_DYNAMIC_LOG(scheduling); using namespace o2::framework; using ConfigurationInterface = o2::configuration::ConfigurationInterface; @@ -119,63 +124,6 @@ void on_idle_timer(uv_timer_t* handle) state->loopReason |= DeviceState::TIMER_EXPIRED; } -bool hasOnlyTimers(DeviceSpec const& spec) -{ - return std::all_of(spec.inputs.cbegin(), spec.inputs.cend(), [](InputRoute const& route) -> bool { return route.matcher.lifetime == Lifetime::Timer; }); -} - -bool hasOnlyGenerated(DeviceSpec const& spec) -{ - return (spec.inputChannels.size() == 1) && (spec.inputs[0].matcher.lifetime == Lifetime::Timer || spec.inputs[0].matcher.lifetime == Lifetime::Enumeration); -} - -void on_transition_requested_expired(uv_timer_t* handle) -{ - auto* ref = (ServiceRegistryRef*)handle->data; - auto& state = ref->get(); - state.loopReason |= DeviceState::TIMER_EXPIRED; - // Check if this is a source device - O2_SIGNPOST_ID_FROM_POINTER(cid, device, handle); - auto& spec = ref->get(); - if (hasOnlyGenerated(spec)) { - O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for source expired. Exiting."); - } else { - O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for %{public}s expired. Exiting.", - state.allowedProcessing == DeviceState::CalibrationOnly ? "calibration" : "data & calibration"); - } - state.transitionHandling = TransitionHandlingState::Expired; -} - -auto switchState(ServiceRegistryRef& ref, StreamingState newState) -> void -{ - auto& state = ref.get(); - auto& context = ref.get(); - O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context); - O2_SIGNPOST_END(device, dpid, "state", "End of processing state %d", (int)state.streaming); - O2_SIGNPOST_START(device, dpid, "state", "Starting processing state %d", (int)newState); - state.streaming = newState; - ref.get().notifyStreamingState(state.streaming); -}; - -void on_data_processing_expired(uv_timer_t* handle) -{ - auto* ref = (ServiceRegistryRef*)handle->data; - auto& state = ref->get(); - auto& spec = ref->get(); - state.loopReason |= DeviceState::TIMER_EXPIRED; - - // Check if this is a source device - O2_SIGNPOST_ID_FROM_POINTER(cid, device, handle); - - if (hasOnlyGenerated(spec)) { - O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for data processing expired. Switching to EndOfStreaming."); - switchState(*ref, StreamingState::EndOfStreaming); - } else { - O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for data processing expired. Only calibrations from this point onwards."); - state.allowedProcessing = DeviceState::CalibrationOnly; - } -} - void on_communication_requested(uv_async_t* s) { auto* state = (DeviceState*)s->data; @@ -194,11 +142,10 @@ struct locked_execution { ~locked_execution() { ref.unlock(); } }; -DataProcessingDevice::DataProcessingDevice(RunningDeviceRef running, ServiceRegistry& registry, ProcessingPolicies& policies) +DataProcessingDevice::DataProcessingDevice(RunningDeviceRef running, ServiceRegistry& registry) : mRunningDevice{running}, mConfigRegistry{nullptr}, - mServiceRegistry{registry}, - mProcessingPolicies{policies} + mServiceRegistry{registry} { GetConfig()->Subscribe("dpl", [®istry = mServiceRegistry](const std::string& key, std::string value) { if (key == "cleanup") { @@ -245,6 +192,7 @@ DataProcessingDevice::DataProcessingDevice(RunningDeviceRef running, ServiceRegi mHandles.resize(1); ServiceRegistryRef ref{mServiceRegistry}; + mAwakeHandle = (uv_async_t*)malloc(sizeof(uv_async_t)); auto& state = ref.get(); assert(state.loop); @@ -264,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; @@ -275,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); } @@ -298,16 +244,22 @@ void run_completion(uv_work_t* handle, int status) static std::function reportConsumedOffer = [ref](ComputingQuotaOffer const& accumulatedConsumed, ComputingQuotaStats& stats) { auto& dpStats = ref.get(); stats.totalConsumedBytes += accumulatedConsumed.sharedMemory; + // For now we give back the offer if we did not use it completely. + // In principle we should try to run until the offer is fully consumed. + stats.totalConsumedTimeslices += std::min(accumulatedConsumed.timeslices, 1); dpStats.updateStats({static_cast(ProcessingStatsId::SHM_OFFER_BYTES_CONSUMED), DataProcessingStats::Op::Set, stats.totalConsumedBytes}); + dpStats.updateStats({static_cast(ProcessingStatsId::TIMESLICE_OFFER_NUMBER_CONSUMED), DataProcessingStats::Op::Set, stats.totalConsumedTimeslices}); dpStats.processCommandQueue(); assert(stats.totalConsumedBytes == dpStats.metrics[(short)ProcessingStatsId::SHM_OFFER_BYTES_CONSUMED]); + assert(stats.totalConsumedTimeslices == dpStats.metrics[(short)ProcessingStatsId::TIMESLICE_OFFER_NUMBER_CONSUMED]); }; static std::function reportExpiredOffer = [ref](ComputingQuotaOffer const& offer, ComputingQuotaStats const& stats) { auto& dpStats = ref.get(); dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCE_OFFER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredOffers}); dpStats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredBytes}); + dpStats.updateStats({static_cast(ProcessingStatsId::TIMESLICE_NUMBER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredTimeslices}); dpStats.processCommandQueue(); }; @@ -448,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(); } @@ -595,76 +546,6 @@ void on_signal_callback(uv_signal_t* handle, int signum) O2_SIGNPOST_END(device, sid, "signal_state", "Done processing signals."); } -static auto toBeForwardedHeader = [](void* header) -> bool { - // If is now possible that the record is not complete when - // we forward it, because of a custom completion policy. - // this means that we need to skip the empty entries in the - // record for being forwarded. - if (header == nullptr) { - return false; - } - auto sih = o2::header::get(header); - if (sih) { - return false; - } - - auto dih = o2::header::get(header); - if (dih) { - return false; - } - - auto dh = o2::header::get(header); - if (!dh) { - return false; - } - auto dph = o2::header::get(header); - if (!dph) { - return false; - } - return true; -}; - -static auto toBeforwardedMessageSet = [](std::vector& cachedForwardingChoices, - FairMQDeviceProxy& proxy, - std::unique_ptr& header, - std::unique_ptr& payload, - size_t total, - bool consume) { - if (header.get() == nullptr) { - // Missing an header is not an error anymore. - // it simply means that we did not receive the - // given input, but we were asked to - // consume existing, so we skip it. - return false; - } - if (payload.get() == nullptr && consume == true) { - // If the payload is not there, it means we already - // processed it with ConsumeExisiting. Therefore we - // need to do something only if this is the last consume. - header.reset(nullptr); - return false; - } - - auto fdph = o2::header::get(header->GetData()); - if (fdph == nullptr) { - LOG(error) << "Data is missing DataProcessingHeader"; - return false; - } - auto fdh = o2::header::get(header->GetData()); - if (fdh == nullptr) { - LOG(error) << "Data is missing DataHeader"; - return false; - } - - // We need to find the forward route only for the first - // part of a split payload. All the others will use the same. - // but always check if we have a sequence of multiple payloads - if (fdh->splitPayloadIndex == 0 || fdh->splitPayloadParts <= 1 || total > 1) { - proxy.getMatchingForwardChannelIndexes(cachedForwardingChoices, *fdh, fdph->startTime); - } - return cachedForwardingChoices.empty() == false; -}; - struct DecongestionContext { ServiceRegistryRef ref; TimesliceIndex::OldestOutputInfo oldestTimeslice; @@ -702,71 +583,15 @@ 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(); - // we collect all messages per forward in a map and send them together - std::vector forwardedParts; - forwardedParts.resize(proxy.getNumForwards()); - std::vector cachedForwardingChoices{}; + O2_SIGNPOST_ID_GENERATE(sid, forwarding); O2_SIGNPOST_START(forwarding, sid, "forwardInputs", "Starting forwarding for slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s", slot.index, oldestTimeslice.timeslice.value, copy ? "with copy" : "", copy && consume ? " and " : "", consume ? "with consume" : ""); + auto forwardedParts = DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copy, consume); - for (size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) { - auto& messageSet = currentSetOfInputs[ii]; - // In case the messageSet is empty, there is nothing to be done. - if (messageSet.size() == 0) { - continue; - } - if (!toBeForwardedHeader(messageSet.header(0)->GetData())) { - continue; - } - cachedForwardingChoices.clear(); - - for (size_t pi = 0; pi < currentSetOfInputs[ii].size(); ++pi) { - auto& messageSet = currentSetOfInputs[ii]; - auto& header = messageSet.header(pi); - auto& payload = messageSet.payload(pi); - auto total = messageSet.getNumberOfPayloads(pi); - - if (!toBeforwardedMessageSet(cachedForwardingChoices, proxy, header, payload, total, consume)) { - continue; - } - - // In case of more than one forward route, we need to copy the message. - // This will eventually use the same mamory if running with the same backend. - if (cachedForwardingChoices.size() > 1) { - copy = true; - } - auto* dh = o2::header::get(header->GetData()); - auto* dph = o2::header::get(header->GetData()); - - if (copy) { - for (auto& cachedForwardingChoice : cachedForwardingChoices) { - auto&& newHeader = header->GetTransport()->CreateMessage(); - O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding a copy of %{public}s to route %d.", - fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), cachedForwardingChoice.value); - newHeader->Copy(*header); - forwardedParts[cachedForwardingChoice.value].AddPart(std::move(newHeader)); - - for (size_t payloadIndex = 0; payloadIndex < messageSet.getNumberOfPayloads(pi); ++payloadIndex) { - auto&& newPayload = header->GetTransport()->CreateMessage(); - newPayload->Copy(*messageSet.payload(pi, payloadIndex)); - forwardedParts[cachedForwardingChoice.value].AddPart(std::move(newPayload)); - } - } - } else { - O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding %{public}s to route %d.", - fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), cachedForwardingChoices.back().value); - forwardedParts[cachedForwardingChoices.back().value].AddPart(std::move(messageSet.header(pi))); - for (size_t payloadIndex = 0; payloadIndex < messageSet.getNumberOfPayloads(pi); ++payloadIndex) { - forwardedParts[cachedForwardingChoices.back().value].AddPart(std::move(messageSet.payload(pi, payloadIndex))); - } - } - } - } - O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding %zu messages", forwardedParts.size()); for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) { if (forwardedParts[fi].Size() == 0) { continue; @@ -790,6 +615,23 @@ 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, + TimesliceIndex::OldestOutputInfo oldestTimeslice, bool copy, bool consume = true) { + auto& proxy = registry.get(); + + O2_SIGNPOST_ID_GENERATE(sid, forwarding); + O2_SIGNPOST_START(forwarding, sid, "forwardInputs", "Cleaning up slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s", + slot.index, oldestTimeslice.timeslice.value, copy ? "with copy" : "", copy && consume ? " and " : "", consume ? "with consume" : ""); + // 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]); + DataProcessingHelpers::cleanForwardedMessages(span, consume); + } + + O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Cleaning done"); +}; + extern volatile int region_read_global_dummy_variable; volatile int region_read_global_dummy_variable; @@ -1187,18 +1029,18 @@ void DataProcessingDevice::fillContext(DataProcessorContext& context, DeviceCont errorCallback(errorContext); }; } else { - context.errorHandling = [&errorPolicy = mProcessingPolicies.error, - &serviceRegistry = mServiceRegistry](RuntimeErrorRef e, InputRecord& record) { + context.errorHandling = [&serviceRegistry = mServiceRegistry](RuntimeErrorRef e, InputRecord& record) { auto& err = error_from_ref(e); /// FIXME: we should pass the salt in, so that the message /// can access information which were stored in the stream. ServiceRegistryRef ref{serviceRegistry, ServiceRegistry::globalDeviceSalt()}; auto& context = ref.get(); + auto& deviceContext = ref.get(); O2_SIGNPOST_ID_FROM_POINTER(cid, device, &context); BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO); auto& stats = ref.get(); stats.updateStats({(int)ProcessingStatsId::EXCEPTION_COUNT, DataProcessingStats::Op::Add, 1}); - switch (errorPolicy) { + switch (deviceContext.processingPolicies.error) { case TerminationPolicy::QUIT: O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "Run", "Exception while running: %{public}s. Rethrowing.", err.what); throw e; @@ -1209,44 +1051,75 @@ void DataProcessingDevice::fillContext(DataProcessorContext& context, DeviceCont }; } - auto decideEarlyForward = [&context, &spec, this]() -> bool { + auto decideEarlyForward = [&context, &deviceContext, &spec, this]() -> ForwardPolicy { + ForwardPolicy defaultEarlyForwardPolicy = getenv("DPL_OLD_EARLY_FORWARD") ? ForwardPolicy::AtCompletionPolicySatisified : ForwardPolicy::AtInjection; + // FIXME: try again with the new policy by default. + // + // Make the new policy optional until we handle some of the corner cases + // with custom policies which expect the early forward to happen only when + // all the data is available, like in the TPC case. + // ForwardPolicy defaultEarlyForwardPolicy = getenv("DPL_NEW_EARLY_FORWARD") ? ForwardPolicy::AtInjection : ForwardPolicy::AtCompletionPolicySatisified; + for (auto& forward : spec.forwards) { + if (DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{"TPC", "DIGITSMCTR"}) || + DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{"TPC", "CLNATIVEMCLBL"}) || + DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "DIGITS"}) || + DataSpecUtils::match(forward.matcher, ConcreteDataTypeMatcher{o2::header::gDataOriginTPC, "CLUSTERNATIVE"})) { + defaultEarlyForwardPolicy = ForwardPolicy::AtCompletionPolicySatisified; + break; + } + } + // Output proxies should wait for the completion policy before forwarding. + // Because they actually do not do anything, that's equivalent to + // forwarding after the processing. + for (auto& label : spec.labels) { + if (label.value == "output-proxy") { + defaultEarlyForwardPolicy = ForwardPolicy::AfterProcessing; + break; + } + } + /// We must make sure there is no optional /// if we want to optimize the forwarding - bool canForwardEarly = (spec.forwards.empty() == false) && mProcessingPolicies.earlyForward != EarlyForwardPolicy::NEVER; + ForwardPolicy forwardPolicy = defaultEarlyForwardPolicy; + if (spec.forwards.empty() == false) { + switch (deviceContext.processingPolicies.earlyForward) { + case o2::framework::EarlyForwardPolicy::NEVER: + forwardPolicy = ForwardPolicy::AfterProcessing; + break; + case o2::framework::EarlyForwardPolicy::ALWAYS: + forwardPolicy = defaultEarlyForwardPolicy; + break; + case o2::framework::EarlyForwardPolicy::NORAW: + forwardPolicy = defaultEarlyForwardPolicy; + break; + } + } bool onlyConditions = true; bool overriddenEarlyForward = false; for (auto& forwarded : spec.forwards) { if (forwarded.matcher.lifetime != Lifetime::Condition) { onlyConditions = false; } -#if !__has_include() - if (strncmp(DataSpecUtils::asConcreteOrigin(forwarded.matcher).str, "AOD", 3) == 0) { - context.canForwardEarly = false; - overriddenEarlyForward = true; - LOG(detail) << "Cannot forward early because of AOD input: " << DataSpecUtils::describe(forwarded.matcher); - break; - } -#endif - if (DataSpecUtils::partialMatch(forwarded.matcher, o2::header::DataDescription{"RAWDATA"}) && mProcessingPolicies.earlyForward == EarlyForwardPolicy::NORAW) { - context.canForwardEarly = false; + if (DataSpecUtils::partialMatch(forwarded.matcher, o2::header::DataDescription{"RAWDATA"}) && deviceContext.processingPolicies.earlyForward == EarlyForwardPolicy::NORAW) { + forwardPolicy = ForwardPolicy::AfterProcessing; overriddenEarlyForward = true; LOG(detail) << "Cannot forward early because of RAWDATA input: " << DataSpecUtils::describe(forwarded.matcher); break; } if (forwarded.matcher.lifetime == Lifetime::Optional) { - context.canForwardEarly = false; + forwardPolicy = ForwardPolicy::AfterProcessing; overriddenEarlyForward = true; LOG(detail) << "Cannot forward early because of Optional input: " << DataSpecUtils::describe(forwarded.matcher); break; } } if (!overriddenEarlyForward && onlyConditions) { - context.canForwardEarly = true; + forwardPolicy = defaultEarlyForwardPolicy; LOG(detail) << "Enabling early forwarding because only conditions to be forwarded"; } - return canForwardEarly; + return forwardPolicy; }; - context.canForwardEarly = decideEarlyForward(); + context.forwardPolicy = decideEarlyForward(); } void DataProcessingDevice::PreRun() @@ -1257,7 +1130,7 @@ void DataProcessingDevice::PreRun() O2_SIGNPOST_ID_FROM_POINTER(cid, device, state.loop); O2_SIGNPOST_START(device, cid, "PreRun", "Entering PreRun callback."); state.quitRequested = false; - switchState(ref, StreamingState::Streaming); + DataProcessingHelpers::switchState(ref, StreamingState::Streaming); state.allowedProcessing = DeviceState::Any; for (auto& info : state.inputChannelInfos) { if (info.state != InputChannelState::Pull) { @@ -1380,51 +1253,7 @@ void DataProcessingDevice::Run() shouldNotWait = true; state.loopReason |= DeviceState::LoopReason::NEW_STATE_PENDING; } - if (state.transitionHandling == TransitionHandlingState::NoTransition && NewStatePending()) { - state.transitionHandling = TransitionHandlingState::Requested; - auto& deviceContext = ref.get(); - // Check if we only have timers - auto& spec = ref.get(); - if (hasOnlyTimers(spec)) { - switchState(ref, StreamingState::EndOfStreaming); - } - - // We do not do anything in particular if the data processing timeout would go past the exitTransitionTimeout - if (deviceContext.dataProcessingTimeout > 0 && deviceContext.dataProcessingTimeout < deviceContext.exitTransitionTimeout) { - uv_update_time(state.loop); - O2_SIGNPOST_EVENT_EMIT(calibration, lid, "timer_setup", "Starting %d s timer for dataProcessingTimeout.", deviceContext.dataProcessingTimeout); - uv_timer_start(deviceContext.dataProcessingGracePeriodTimer, on_data_processing_expired, deviceContext.dataProcessingTimeout * 1000, 0); - } - if (deviceContext.exitTransitionTimeout != 0 && state.streaming != StreamingState::Idle) { - state.transitionHandling = TransitionHandlingState::Requested; - ref.get().call(ServiceRegistryRef{ref}); - uv_update_time(state.loop); - O2_SIGNPOST_EVENT_EMIT(calibration, lid, "timer_setup", "Starting %d s timer for exitTransitionTimeout.", - deviceContext.exitTransitionTimeout); - uv_timer_start(deviceContext.gracePeriodTimer, on_transition_requested_expired, deviceContext.exitTransitionTimeout * 1000, 0); - bool onlyGenerated = hasOnlyGenerated(spec); - int timeout = onlyGenerated ? deviceContext.dataProcessingTimeout : deviceContext.exitTransitionTimeout; - if (mProcessingPolicies.termination == TerminationPolicy::QUIT && DefaultsHelpers::onlineDeploymentMode() == false) { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. Waiting for %d seconds before quitting.", timeout); - } else { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", - "New state requested. Waiting for %d seconds before %{public}s", - timeout, - onlyGenerated ? "dropping remaining input and switching to READY state." : "switching to READY state."); - } - } else { - state.transitionHandling = TransitionHandlingState::Expired; - if (deviceContext.exitTransitionTimeout == 0 && mProcessingPolicies.termination == TerminationPolicy::QUIT) { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. No timeout set, quitting immediately as per --completion-policy"); - } else if (deviceContext.exitTransitionTimeout == 0 && mProcessingPolicies.termination != TerminationPolicy::QUIT) { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. No timeout set, switching to READY state immediately"); - } else if (mProcessingPolicies.termination == TerminationPolicy::QUIT) { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state pending and we are already idle, quitting immediately as per --completion-policy"); - } else { - O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state pending and we are already idle, switching to READY immediately."); - } - } - } + state.transitionHandling = DataProcessingHelpers::updateStateTransition(ref, ref.get().processingPolicies); // If we are Idle, we can then consider the transition to be expired. if (state.transitionHandling == TransitionHandlingState::Requested && state.streaming == StreamingState::Idle) { O2_SIGNPOST_EVENT_EMIT(device, lid, "run_loop", "State transition requested and we are now in Idle. We can consider it to be completed."); @@ -1447,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(); @@ -1501,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}; @@ -1530,6 +1363,7 @@ void DataProcessingDevice::Run() auto& dpStats = ref.get(); dpStats.updateStats({static_cast(ProcessingStatsId::RESOURCE_OFFER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredOffers}); dpStats.updateStats({static_cast(ProcessingStatsId::ARROW_BYTES_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredBytes}); + dpStats.updateStats({static_cast(ProcessingStatsId::TIMESLICE_NUMBER_EXPIRED), DataProcessingStats::Op::Set, stats.totalExpiredTimeslices}); dpStats.processCommandQueue(); }; auto ref = ServiceRegistryRef{mServiceRegistry}; @@ -1538,12 +1372,27 @@ 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); if (enough) { stream.id = streamRef; stream.running = true; stream.registry = &mServiceRegistry; + 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; uv_queue_work(state.loop, stream.task, run_callback, run_completion); @@ -1552,13 +1401,62 @@ void DataProcessingDevice::Run() run_completion(&handle, 0); } } else { + 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 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.numberOfUnscheduled++; + schedulingStats.numberOfUnscheduledSinceLastScheduled++; auto ref = ServiceRegistryRef{mServiceRegistry}; ref.get().handleExpired(reportExpiredOffer); } } } - O2_SIGNPOST_END(device, lid, "run_loop", "Run loop completed. Transition handling state %d.", state.transitionHandling); + O2_SIGNPOST_END(device, lid, "run_loop", "Run loop completed. Transition handling state %d.", (int)state.transitionHandling); auto& spec = ref.get(); /// Cleanup messages which are still pending on exit. for (size_t ci = 0; ci < spec.inputChannels.size(); ++ci) { @@ -1625,7 +1523,7 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) auto& infos = state.inputChannelInfos; if (context.balancingInputs) { - static int pipelineLength = DefaultsHelpers::pipelineLength(); + static int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get().device()->fConfig); static uint64_t ahead = getenv("DPL_MAX_CHANNEL_AHEAD") ? std::atoll(getenv("DPL_MAX_CHANNEL_AHEAD")) : std::max(8, std::min(pipelineLength - 48, pipelineLength / 2)); auto newEnd = std::remove_if(pollOrder.begin(), pollOrder.end(), [&infos, limitNew = currentOldest.value + ahead](int a) -> bool { return infos[a].oldestForChannel.value > limitNew; @@ -1651,9 +1549,8 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) for (auto sci : pollOrder) { auto& info = state.inputChannelInfos[sci]; - auto& channelSpec = spec.inputChannels[sci]; O2_SIGNPOST_ID_FROM_POINTER(cid, device, &info); - O2_SIGNPOST_START(device, cid, "channels", "Processing channel %s", channelSpec.name.c_str()); + O2_SIGNPOST_START(device, cid, "channels", "Processing channel %s", info.channel->GetName().c_str()); if (info.state != InputChannelState::Completed && info.state != InputChannelState::Pull) { context.allDone = false; @@ -1665,18 +1562,18 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) DataProcessingDevice::handleData(ref, info); } O2_SIGNPOST_END(device, cid, "channels", "Flushing channel %s which is in state %d and has %zu parts still pending.", - channelSpec.name.c_str(), (int)info.state, info.parts.Size()); + info.channel->GetName().c_str(), (int)info.state, info.parts.Size()); continue; } if (info.channel == nullptr) { O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is nullptr and has %zu parts still pending.", - channelSpec.name.c_str(), (int)info.state, info.parts.Size()); + info.channel->GetName().c_str(), (int)info.state, info.parts.Size()); continue; } // Only poll DPL channels for now. if (info.channelType != ChannelAccountingType::DPL) { O2_SIGNPOST_END(device, cid, "channels", "Channel %s which is in state %d is not a DPL channel and has %zu parts still pending.", - channelSpec.name.c_str(), (int)info.state, info.parts.Size()); + info.channel->GetName().c_str(), (int)info.state, info.parts.Size()); continue; } auto& socket = info.channel->GetSocket(); @@ -1688,7 +1585,7 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) socket.Events(&info.hasPendingEvents); // If we do not read, we can continue. if ((info.hasPendingEvents & 1) == 0 && (info.parts.Size() == 0)) { - O2_SIGNPOST_END(device, cid, "channels", "No pending events and no remaining parts to process for channel %{public}s", channelSpec.name.c_str()); + O2_SIGNPOST_END(device, cid, "channels", "No pending events and no remaining parts to process for channel %{public}s", info.channel->GetName().c_str()); continue; } } @@ -1706,12 +1603,12 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) bool newMessages = false; while (true) { O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Receiving loop called for channel %{public}s (%d) with oldest possible timeslice %zu", - channelSpec.name.c_str(), info.id.value, info.oldestForChannel.value); + info.channel->GetName().c_str(), info.id.value, info.oldestForChannel.value); if (info.parts.Size() < 64) { fair::mq::Parts parts; info.channel->Receive(parts, 0); if (parts.Size()) { - O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Received %zu parts from channel %{public}s (%d).", parts.Size(), channelSpec.name.c_str(), info.id.value); + O2_SIGNPOST_EVENT_EMIT(device, cid, "channels", "Received %zu parts from channel %{public}s (%d).", parts.Size(), info.channel->GetName().c_str(), info.id.value); } for (auto&& part : parts) { info.parts.fParts.emplace_back(std::move(part)); @@ -1740,7 +1637,7 @@ void DataProcessingDevice::doPrepare(ServiceRegistryRef ref) } } O2_SIGNPOST_END(device, cid, "channels", "Done processing channel %{public}s (%d).", - channelSpec.name.c_str(), info.id.value); + info.channel->GetName().c_str(), info.id.value); } } @@ -1783,7 +1680,7 @@ void DataProcessingDevice::doRun(ServiceRegistryRef ref) // dependent on the callback, not something which is controlled by the // framework itself. if (context.allDone == true && state.streaming == StreamingState::Streaming) { - switchState(ref, StreamingState::EndOfStreaming); + DataProcessingHelpers::switchState(ref, StreamingState::EndOfStreaming); state.lastActiveDataProcessor = &context; } @@ -1796,7 +1693,7 @@ void DataProcessingDevice::doRun(ServiceRegistryRef ref) /// timers as they do not need to be further processed. auto& relayer = ref.get(); - bool shouldProcess = hasOnlyGenerated(spec) == false; + bool shouldProcess = DataProcessingHelpers::hasOnlyGenerated(spec) == false; while (DataProcessingDevice::tryDispatchComputation(ref, context.completed) && shouldProcess) { relayer.processDanglingInputs(context.expirationHandlers, *context.registry, false); @@ -1829,7 +1726,7 @@ void DataProcessingDevice::doRun(ServiceRegistryRef ref) } // This is needed because the transport is deleted before the device. relayer.clear(); - switchState(ref, StreamingState::Idle); + DataProcessingHelpers::switchState(ref, StreamingState::Idle); // In case we should process, note the data processor responsible for it if (shouldProcess) { state.lastActiveDataProcessor = &context; @@ -1878,6 +1775,51 @@ struct WaitBackpressurePolicy { } }; +auto forwardOnInsertion(ServiceRegistryRef& ref, std::span& messages) -> void +{ + O2_SIGNPOST_ID_GENERATE(sid, forwarding); + + auto& spec = ref.get(); + auto& context = ref.get(); + if (context.forwardPolicy == ForwardPolicy::AfterProcessing || spec.forwards.empty()) { + O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding not enabled / needed."); + return; + } + + O2_SIGNPOST_EVENT_EMIT(device, sid, "device", "Early forwardinding before injecting data into relayer."); + auto& timesliceIndex = ref.get(); + auto oldestTimeslice = timesliceIndex.getOldestPossibleOutput(); + + auto& proxy = ref.get(); + + O2_SIGNPOST_START(forwarding, sid, "forwardInputs", + "Starting forwarding for incoming messages with oldestTimeslice %zu with copy", + oldestTimeslice.timeslice.value); + std::vector forwardedParts(proxy.getNumForwardChannels()); + DataProcessingHelpers::routeForwardedMessages(proxy, messages, forwardedParts, true, false); + + for (int fi = 0; fi < proxy.getNumForwardChannels(); fi++) { + if (forwardedParts[fi].Size() == 0) { + continue; + } + ForwardChannelInfo info = proxy.getForwardChannelInfo(ChannelIndex{fi}); + auto& parts = forwardedParts[fi]; + if (info.policy == nullptr) { + O2_SIGNPOST_EVENT_EMIT_ERROR(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d has no policy.", info.name.c_str(), fi); + continue; + } + O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding to %{public}s %d", info.name.c_str(), fi); + info.policy->forward(parts, ChannelIndex{fi}, ref); + } + auto& asyncQueue = ref.get(); + auto& decongestion = ref.get(); + O2_SIGNPOST_ID_GENERATE(aid, async_queue); + O2_SIGNPOST_EVENT_EMIT(async_queue, aid, "forwardInputs", "Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value); + AsyncQueueHelpers::post(asyncQueue, AsyncTask{.timeslice = oldestTimeslice.timeslice, .id = decongestion.oldestPossibleTimesliceTask, .debounce = -1, .callback = decongestionCallbackLate} + .user({.ref = ref, .oldestTimeslice = oldestTimeslice})); + O2_SIGNPOST_END(forwarding, sid, "forwardInputs", "Forwarding done"); +}; + /// This is the inner loop of our framework. The actual implementation /// is divided in two parts. In the first one we define a set of lambdas /// which describe what is actually going to happen, hiding all the state @@ -1920,11 +1862,15 @@ void DataProcessingDevice::handleData(ServiceRegistryRef ref, InputChannelInfo& for (size_t pi = 0; pi < parts.Size(); pi += 2) { auto* headerData = parts.At(pi)->GetData(); auto sih = o2::header::get(headerData); + auto dh = o2::header::get(headerData); if (sih) { O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got SourceInfoHeader with state %d", (int)sih->state); info.state = sih->state; insertInputInfo(pi, 2, InputType::SourceInfo, info.id); state.lastActiveDataProcessor = &context; + if (dh) { + LOGP(error, "Found data attached to a SourceInfoHeader"); + } continue; } auto dih = o2::header::get(headerData); @@ -1932,9 +1878,11 @@ void DataProcessingDevice::handleData(ServiceRegistryRef ref, InputChannelInfo& O2_SIGNPOST_EVENT_EMIT(device, cid, "handle_data", "Got DomainInfoHeader with oldestPossibleTimeslice %d", (int)dih->oldestPossibleTimeslice); insertInputInfo(pi, 2, InputType::DomainInfo, info.id); state.lastActiveDataProcessor = &context; + if (dh) { + LOGP(error, "Found data attached to a DomainInfoHeader"); + } continue; } - auto dh = o2::header::get(headerData); if (!dh) { insertInputInfo(pi, 0, InputType::Invalid, info.id); O2_SIGNPOST_EVENT_EMIT_ERROR(device, cid, "handle_data", "Header is not a DataHeader?"); @@ -1991,7 +1939,7 @@ void DataProcessingDevice::handleData(ServiceRegistryRef ref, InputChannelInfo& stats.updateStats({(int)ProcessingStatsId::ERROR_COUNT, DataProcessingStats::Op::Add, 1}); }; - auto handleValidMessages = [&info, ref, &reportError](std::vector const& inputInfos) { + auto handleValidMessages = [&info, ref, &reportError, &context](std::vector const& inputInfos) { auto& relayer = ref.get(); auto& state = ref.get(); static WaitBackpressurePolicy policy; @@ -2034,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); @@ -2046,11 +1994,13 @@ void DataProcessingDevice::handleData(ServiceRegistryRef ref, InputChannelInfo& VariableContextHelpers::getTimeslice(variables); forwardInputs(ref, slot, dropped, oldestOutputInfo, false, true); }; + auto relayed = relayer.relay(parts.At(headerIndex)->GetData(), &parts.At(headerIndex), input, nMessages, nPayloadsPerHeader, + context.forwardPolicy == ForwardPolicy::AtInjection ? forwardOnInsertion : nullptr, onDrop); switch (relayed.type) { case DataRelayer::RelayChoice::Type::Backpressured: @@ -2210,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) { @@ -2220,37 +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{}; + } + 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{}; + 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{}); }; -#if __has_include() 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(); }; -#else - std::function refCountGetter = nullptr; -#endif - 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 { @@ -2353,12 +2303,14 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v return false; } - auto postUpdateStats = [ref](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t tStart, uint64_t tStartMilli) { + int pipelineLength = DefaultsHelpers::pipelineLength(*ref.get().device()->fConfig); + + auto postUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t tStart, uint64_t tStartMilli) { auto& stats = ref.get(); auto& states = ref.get(); std::atomic_thread_fence(std::memory_order_release); char relayerSlotState[1024]; - int written = snprintf(relayerSlotState, 1024, "%d ", DefaultsHelpers::pipelineLength()); + int written = snprintf(relayerSlotState, 1024, "%d ", pipelineLength); char* buffer = relayerSlotState + written; for (size_t ai = 0; ai != record.size(); ai++) { buffer[ai] = record.isValid(ai) ? '3' : '0'; @@ -2385,11 +2337,11 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v count++; }; - auto preUpdateStats = [ref](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t) { + auto preUpdateStats = [ref, pipelineLength](DataRelayer::RecordAction const& action, InputRecord const& record, uint64_t) { auto& states = ref.get(); std::atomic_thread_fence(std::memory_order_release); char relayerSlotState[1024]; - snprintf(relayerSlotState, 1024, "%d ", DefaultsHelpers::pipelineLength()); + snprintf(relayerSlotState, 1024, "%d ", pipelineLength); char* buffer = strchr(relayerSlotState, ' ') + 1; for (size_t ai = 0; ai != record.size(); ai++) { buffer[ai] = record.isValid(ai) ? '2' : '0'; @@ -2468,11 +2420,23 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v bool hasForwards = spec.forwards.empty() == false; bool consumeSomething = action.op == CompletionPolicy::CompletionOp::Consume || action.op == CompletionPolicy::CompletionOp::ConsumeExisting; - if (context.canForwardEarly && hasForwards && consumeSomething) { - O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Early forwainding: %{public}s.", fmt::format("{}", action.op).c_str()); + if (context.forwardPolicy == ForwardPolicy::AtCompletionPolicySatisified && hasForwards && consumeSomething) { + O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Early forwarding: %{public}s.", fmt::format("{}", action.op).c_str()); auto& timesliceIndex = ref.get(); forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume); + } else if (context.forwardPolicy == ForwardPolicy::AtInjection && hasForwards && consumeSomething) { + // We used to do fowarding here, however we now do it much earlier. + // We still need to clean the inputs which were already consumed + // via ConsumeExisting and which still have an header to hold the slot. + // FIXME: do we? This should really happen when we do the forwarding on + // insertion, because otherwise we lose the relevant information on how to + // navigate the set of headers. We could actually rely on the messageset index, + // is that the right thing to do though? + O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "cleaning early forwarding: %{public}s.", fmt::format("{}", action.op).c_str()); + auto& timesliceIndex = ref.get(); + cleanEarlyForward(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), true, action.op == CompletionPolicy::CompletionOp::Consume); } + markInputsAsDone(action.slot); uint64_t tStart = uv_hrtime(); @@ -2522,7 +2486,7 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "Skipping processing because we are discarding."); } else { O2_SIGNPOST_EVENT_EMIT(device, pcid, "device", "No processing callback provided. Switching to %{public}s.", "Idle"); - switchState(ref, StreamingState::Idle); + DataProcessingHelpers::switchState(ref, StreamingState::Idle); } if (shouldProcess(action)) { auto& timingInfo = ref.get(); @@ -2591,7 +2555,7 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v context.postDispatchingCallbacks(processContext); ref.get().call(o2::framework::ServiceRegistryRef{ref}); } - if ((context.canForwardEarly == false) && hasForwards && consumeSomething) { + if ((context.forwardPolicy == ForwardPolicy::AfterProcessing) && hasForwards && consumeSomething) { O2_SIGNPOST_EVENT_EMIT(device, aid, "device", "Late forwarding"); auto& timesliceIndex = ref.get(); forwardInputs(ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(), false, action.op == CompletionPolicy::CompletionOp::Consume); @@ -2610,7 +2574,7 @@ bool DataProcessingDevice::tryDispatchComputation(ServiceRegistryRef ref, std::v for (auto& channel : spec.outputChannels) { DataProcessingHelpers::sendEndOfStream(ref, channel); } - switchState(ref, StreamingState::Idle); + DataProcessingHelpers::switchState(ref, StreamingState::Idle); } return true; diff --git a/Framework/Core/src/DataProcessingHelpers.cxx b/Framework/Core/src/DataProcessingHelpers.cxx index 58fda5a13bae9..b8399a4c591e7 100644 --- a/Framework/Core/src/DataProcessingHelpers.cxx +++ b/Framework/Core/src/DataProcessingHelpers.cxx @@ -16,14 +16,37 @@ #include "MemoryResources/MemoryResources.h" #include "Framework/FairMQDeviceProxy.h" #include "Headers/DataHeader.h" +#include "Headers/DataHeaderHelpers.h" #include "Headers/Stack.h" #include "Framework/Logger.h" #include "Framework/SendingPolicy.h" #include "Framework/RawDeviceService.h" +#include "Framework/DeviceState.h" +#include "Framework/DeviceContext.h" +#include "Framework/ProcessingPolicies.h" +#include "Framework/Signpost.h" +#include "Framework/CallbackService.h" +#include "Framework/DefaultsHelpers.h" +#include "Framework/ServiceRegistryRef.h" +#include "Framework/DeviceSpec.h" +#include "Framework/ControlService.h" +#include "Framework/DataProcessingContext.h" +#include "Framework/DeviceStateEnums.h" +#include "Headers/DataHeader.h" +#include "Framework/DataProcessingHeader.h" +#include "DecongestionService.h" #include #include +#include + +// A log to use for general device logging +O2_DECLARE_DYNAMIC_LOG(device); +// Stream which keeps track of the calibration lifetime logic +O2_DECLARE_DYNAMIC_LOG(calibration); +O2_DECLARE_DYNAMIC_LOG(forwarding); + namespace o2::framework { void DataProcessingHelpers::sendEndOfStream(ServiceRegistryRef const& ref, OutputChannelSpec const& channel) @@ -61,6 +84,9 @@ void doSendOldestPossibleTimeframe(ServiceRegistryRef ref, fair::mq::TransportFa bool DataProcessingHelpers::sendOldestPossibleTimeframe(ServiceRegistryRef const& ref, ForwardChannelInfo const& info, ForwardChannelState& state, size_t timeslice) { + if (ref.get().suppressDomainInfo) { + return false; + } if (state.oldestForChannel.value >= timeslice) { return false; } @@ -71,6 +97,9 @@ bool DataProcessingHelpers::sendOldestPossibleTimeframe(ServiceRegistryRef const bool DataProcessingHelpers::sendOldestPossibleTimeframe(ServiceRegistryRef const& ref, OutputChannelInfo const& info, OutputChannelState& state, size_t timeslice) { + if (ref.get().suppressDomainInfo) { + return false; + } if (state.oldestForChannel.value >= timeslice) { return false; } @@ -89,4 +118,292 @@ void DataProcessingHelpers::broadcastOldestPossibleTimeslice(ServiceRegistryRef } } +void DataProcessingHelpers::switchState(ServiceRegistryRef const& ref, StreamingState newState) +{ + auto& state = ref.get(); + auto& context = ref.get(); + O2_SIGNPOST_ID_FROM_POINTER(dpid, device, &context); + O2_SIGNPOST_END(device, dpid, "state", "End of processing state %d", (int)state.streaming); + O2_SIGNPOST_START(device, dpid, "state", "Starting processing state %d", (int)newState); + state.streaming = newState; + ref.get().notifyStreamingState(state.streaming); +}; + +bool hasOnlyTimers(DeviceSpec const& spec) +{ + return std::all_of(spec.inputs.cbegin(), spec.inputs.cend(), [](InputRoute const& route) -> bool { return route.matcher.lifetime == Lifetime::Timer; }); +} + +bool DataProcessingHelpers::hasOnlyGenerated(DeviceSpec const& spec) +{ + return (spec.inputChannels.size() == 1) && (spec.inputs[0].matcher.lifetime == Lifetime::Timer || spec.inputs[0].matcher.lifetime == Lifetime::Enumeration); +} + +void on_data_processing_expired(uv_timer_t* handle) +{ + auto* ref = (ServiceRegistryRef*)handle->data; + auto& state = ref->get(); + auto& spec = ref->get(); + state.loopReason |= DeviceState::TIMER_EXPIRED; + + // Check if this is a source device + O2_SIGNPOST_ID_FROM_POINTER(cid, calibration, handle); + + if (DataProcessingHelpers::hasOnlyGenerated(spec)) { + O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for data processing expired. Switching to EndOfStreaming."); + DataProcessingHelpers::switchState(*ref, StreamingState::EndOfStreaming); + } else { + O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "callback", "Grace period for data processing expired. Only calibrations from this point onwards."); + state.allowedProcessing = DeviceState::CalibrationOnly; + } +} + +void on_transition_requested_expired(uv_timer_t* handle) +{ + auto* ref = (ServiceRegistryRef*)handle->data; + auto& state = ref->get(); + state.loopReason |= DeviceState::TIMER_EXPIRED; + // Check if this is a source device + O2_SIGNPOST_ID_FROM_POINTER(cid, calibration, handle); + auto& spec = ref->get(); + std::string messageOnExpire = DataProcessingHelpers::hasOnlyGenerated(spec) ? "DPL exit transition grace period for source expired. Exiting." : fmt::format("DPL exit transition grace period for {} expired. Exiting.", state.allowedProcessing == DeviceState::CalibrationOnly ? "calibration" : "data & calibration").c_str(); + if (!ref->get().device()->GetConfig()->GetValue("error-on-exit-transition-timeout")) { + O2_SIGNPOST_EVENT_EMIT_WARN(calibration, cid, "callback", "%{public}s", messageOnExpire.c_str()); + } else { + O2_SIGNPOST_EVENT_EMIT_ERROR(calibration, cid, "callback", "%{public}s", messageOnExpire.c_str()); + } + state.transitionHandling = TransitionHandlingState::Expired; +} + +TransitionHandlingState DataProcessingHelpers::updateStateTransition(ServiceRegistryRef const& ref, ProcessingPolicies const& policies) +{ + auto& state = ref.get(); + auto& deviceProxy = ref.get(); + if (state.transitionHandling != TransitionHandlingState::NoTransition || deviceProxy.newStateRequested() == false) { + return state.transitionHandling; + } + O2_SIGNPOST_ID_FROM_POINTER(lid, device, state.loop); + O2_SIGNPOST_ID_FROM_POINTER(cid, calibration, state.loop); + auto& deviceContext = ref.get(); + // Check if we only have timers + auto& spec = ref.get(); + if (hasOnlyTimers(spec)) { + DataProcessingHelpers::switchState(ref, StreamingState::EndOfStreaming); + } + + // We do not do anything in particular if the data processing timeout would go past the exitTransitionTimeout + if (deviceContext.dataProcessingTimeout > 0 && deviceContext.dataProcessingTimeout < deviceContext.exitTransitionTimeout) { + uv_update_time(state.loop); + O2_SIGNPOST_EVENT_EMIT(calibration, cid, "timer_setup", "Starting %d s timer for dataProcessingTimeout.", deviceContext.dataProcessingTimeout); + uv_timer_start(deviceContext.dataProcessingGracePeriodTimer, on_data_processing_expired, deviceContext.dataProcessingTimeout * 1000, 0); + } + if (deviceContext.exitTransitionTimeout != 0 && state.streaming != StreamingState::Idle) { + ref.get().call(ServiceRegistryRef{ref}); + uv_update_time(state.loop); + O2_SIGNPOST_EVENT_EMIT(calibration, cid, "timer_setup", "Starting %d s timer for exitTransitionTimeout.", + deviceContext.exitTransitionTimeout); + uv_timer_start(deviceContext.gracePeriodTimer, on_transition_requested_expired, deviceContext.exitTransitionTimeout * 1000, 0); + bool onlyGenerated = DataProcessingHelpers::hasOnlyGenerated(spec); + int timeout = onlyGenerated ? deviceContext.dataProcessingTimeout : deviceContext.exitTransitionTimeout; + if (policies.termination == TerminationPolicy::QUIT && DefaultsHelpers::onlineDeploymentMode() == false) { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. Waiting for %d seconds before quitting.", timeout); + } else { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", + "New state requested. Waiting for %d seconds before %{public}s", + timeout, + onlyGenerated ? "dropping remaining input and switching to READY state." : "switching to READY state."); + } + return TransitionHandlingState::Requested; + } else { + if (deviceContext.exitTransitionTimeout == 0 && policies.termination == TerminationPolicy::QUIT) { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. No timeout set, quitting immediately as per --completion-policy"); + } else if (deviceContext.exitTransitionTimeout == 0 && policies.termination != TerminationPolicy::QUIT) { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state requested. No timeout set, switching to READY state immediately"); + } else if (policies.termination == TerminationPolicy::QUIT) { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state pending and we are already idle, quitting immediately as per --completion-policy"); + } else { + O2_SIGNPOST_EVENT_EMIT_INFO(device, lid, "run_loop", "New state pending and we are already idle, switching to READY immediately."); + } + return TransitionHandlingState::Expired; + } +} + +void DataProcessingHelpers::routeForwardedMessages(FairMQDeviceProxy& proxy, std::span& messages, std::vector& forwardedParts, + const bool copyByDefault, bool consume) +{ + O2_SIGNPOST_ID_GENERATE(sid, forwarding); + std::vector forwardingChoices{}; + size_t pi = 0; + while (pi < messages.size()) { + auto& header = messages[pi]; + + // If is now possible that the record is not complete when + // we forward it, because of a custom completion policy. + // this means that we need to skip the empty entries in the + // record for being forwarded. + if (header->GetData() == nullptr) { + pi += 2; + continue; + } + auto dih = o2::header::get(header->GetData()); + if (dih) { + pi += 2; + continue; + } + auto sih = o2::header::get(header->GetData()); + if (sih) { + pi += 2; + continue; + } + + auto dph = o2::header::get(header->GetData()); + auto dh = o2::header::get(header->GetData()); + + if (dph == nullptr || dh == nullptr) { + // Complain only if this is not an out-of-band message + LOGP(error, "Data is missing {}{}{}", + dph ? "DataProcessingHeader" : "", dph || dh ? "and" : "", dh ? "DataHeader" : ""); + pi += 2; + continue; + } + + // At least one payload. + auto& payload = messages[pi + 1]; + // Calculate the number of messages which should be handled together + // all in one go. + size_t numberOfMessages = 0; + if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) { + // Sequence of (header, payload[0], ... , payload[splitPayloadParts - 1]) pairs belonging together. + numberOfMessages = dh->splitPayloadParts + 1; // one is for the header + } else { + // Sequence of splitPayloadParts (header, payload) pairs belonging together. + // In case splitPayloadParts = 0, we consider this as a single message pair + numberOfMessages = (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2; + } + + if (payload.get() == nullptr && consume == true) { + // If the payload is not there, it means we already + // processed it with ConsumeExisiting. Therefore we + // need to do something only if this is the last consume. + header.reset(nullptr); + pi += numberOfMessages; + continue; + } + + // We need to find the forward route only for the first + // part of a split payload. All the others will use the same. + // Therefore, we reset and recompute the forwarding choice: + // + // - If this is the first payload of a [header0][payload0][header0][payload1]... sequence, + // which is actually always created and handled together. Notice that in this + // case we have splitPayloadParts == splitPayloadIndex + // - If this is the first payload of a [header0][payload0][header1][payload1]... sequence + // belonging to the same multipart message (and therefore we are guaranteed that they + // need to be routed together). + // - If the message is not a multipart (splitPayloadParts 0) or has only one part + // - If it's a message of the kind [header0][payload1][payload2][payload3]... and therefore + // we will already use the same choice in the for loop below. + // + + forwardingChoices.clear(); + proxy.getMatchingForwardChannelIndexes(forwardingChoices, *dh, dph->startTime); + + if (forwardingChoices.empty()) { + // Nothing to forward go to the next messageset + pi += numberOfMessages; + continue; + } + + // In case of more than one forward route, we need to copy the message. + // This will eventually use the same memory if running with the same backend. + if (copyByDefault || forwardingChoices.size() > 1) { + for (auto& choice : forwardingChoices) { + O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding a copy of %{public}s to route %d.", + fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), choice.value); + + for (size_t ppi = pi; ppi < pi + numberOfMessages; ++ppi) { + auto&& newMsg = header->GetTransport()->CreateMessage(); + newMsg->Copy(*messages[ppi]); + forwardedParts[choice.value].AddPart(std::move(newMsg)); + } + } + } else { + O2_SIGNPOST_EVENT_EMIT(forwarding, sid, "forwardInputs", "Forwarding %{public}s to route %d.", + fmt::format("{}/{}/{}@timeslice:{} tfCounter:{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification, dph->startTime, dh->tfCounter).c_str(), forwardingChoices.back().value); + for (size_t ppi = pi; ppi < pi + numberOfMessages; ++ppi) { + forwardedParts[forwardingChoices.back().value].AddPart(std::move(messages[ppi])); + } + } + pi += numberOfMessages; + } +} + +void DataProcessingHelpers::cleanForwardedMessages(std::span& messages, bool consume) +{ + size_t pi = 0; + while (pi < messages.size()) { + auto& header = messages[pi]; + + // If is now possible that the record is not complete when + // we forward it, because of a custom completion policy. + // this means that we need to skip the empty entries in the + // record for being forwarded. + if (header->GetData() == nullptr || + o2::header::get(header->GetData()) || + o2::header::get(header->GetData())) { + pi += 2; + continue; + } + + auto dph = o2::header::get(header->GetData()); + auto dh = o2::header::get(header->GetData()); + + if (dph == nullptr || dh == nullptr) { + // Complain only if this is not an out-of-band message + LOGP(error, "Data is missing {}{}{}", + dph ? "DataProcessingHeader" : "", dph || dh ? "and" : "", dh ? "DataHeader" : ""); + pi += 2; + continue; + } + + // At least one payload. + auto& payload = messages[pi + 1]; + // Calculate the number of messages which should be handled together + // all in one go. + size_t numberOfMessages = 0; + if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) { + // Sequence of (header, payload[0], ... , payload[splitPayloadParts - 1]) pairs belonging together. + numberOfMessages = dh->splitPayloadParts + 1; // one is for the header + } else { + // Sequence of splitPayloadParts (header, payload) pairs belonging together. + // In case splitPayloadParts = 0, we consider this as a single message pair + numberOfMessages = (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2; + } + + if (payload.get() == nullptr && consume == true) { + // If the payload is not there, it means we already + // processed it with ConsumeExisiting. Therefore we + // need to do something only if this is the last consume. + header.reset(nullptr); + } + + // Nothing to forward go to the next messageset + pi += numberOfMessages; + } +} + +auto DataProcessingHelpers::routeForwardedMessageSet(FairMQDeviceProxy& proxy, + 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]); + routeForwardedMessages(proxy, span, forwardedParts, copyByDefault, consume); + } + return forwardedParts; +}; + } // namespace o2::framework 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/DataProcessingStats.cxx b/Framework/Core/src/DataProcessingStats.cxx index 3b02a0aacdd70..8349af62acdc2 100644 --- a/Framework/Core/src/DataProcessingStats.cxx +++ b/Framework/Core/src/DataProcessingStats.cxx @@ -29,6 +29,16 @@ DataProcessingStats::DataProcessingStats(std::function& offers, + std::array& offers, ComputingQuotaStats& stats, std::function accountDisposed) { ComputingQuotaOffer disposed; diff --git a/Framework/Core/src/DataRefUtils.cxx b/Framework/Core/src/DataRefUtils.cxx index 69eb1dc7faba6..894ad0b8ace63 100644 --- a/Framework/Core/src/DataRefUtils.cxx +++ b/Framework/Core/src/DataRefUtils.cxx @@ -9,6 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +#include #include #include #include "Framework/DataRefUtils.h" @@ -148,4 +149,19 @@ std::map DataRefUtils::extractCCDBHeaders(DataRef cons return res; } +std::span DataRefUtils::getCCDBPayloadBlob(DataRef const& ref) +{ + auto* dh = o2::header::get(ref.header); + const char* buff = ref.payload; + size_t payloadSize = dh->payloadSize; + constexpr char FlatHeaderAnnot[] = "$HEADER$"; + constexpr size_t Offset = sizeof(int) + sizeof(FlatHeaderAnnot); + int headerSize = 0; + if (payloadSize >= Offset && + !std::strncmp(buff + payloadSize - sizeof(FlatHeaderAnnot), FlatHeaderAnnot, sizeof(FlatHeaderAnnot))) { + headerSize = *reinterpret_cast(buff + payloadSize - Offset); + } + return {buff, payloadSize - headerSize}; +} + } // namespace o2::framework diff --git a/Framework/Core/src/DataRelayer.cxx b/Framework/Core/src/DataRelayer.cxx index 092e8340a934a..7adf5b5c97fbb 100644 --- a/Framework/Core/src/DataRelayer.cxx +++ b/Framework/Core/src/DataRelayer.cxx @@ -37,6 +37,8 @@ #include "Framework/DataProcessingStates.h" #include "Framework/DataTakingContext.h" #include "Framework/DefaultsHelpers.h" +#include "Framework/RawDeviceService.h" +#include "Framework/DataModelViews.h" #include "Headers/DataHeaderHelpers.h" #include "Framework/Formatters.h" @@ -47,12 +49,11 @@ #include #include #include -#if __has_include() #include -#endif +#include #include #include -#include +#include #include using namespace o2::framework::data_matcher; @@ -72,7 +73,8 @@ constexpr int INVALID_INPUT = -1; DataRelayer::DataRelayer(const CompletionPolicy& policy, std::vector const& routes, TimesliceIndex& index, - ServiceRegistryRef services) + ServiceRegistryRef services, + int pipelineLength) : mContext{services}, mTimesliceIndex{index}, mCompletionPolicy{policy}, @@ -83,7 +85,17 @@ DataRelayer::DataRelayer(const CompletionPolicy& policy, std::scoped_lock lock(mMutex); if (policy.configureRelayer == nullptr) { - static int pipelineLength = DefaultsHelpers::pipelineLength(); + if (pipelineLength == -1) { + auto getPipelineLengthHelper = [&services]() { + try { + return DefaultsHelpers::pipelineLength(*services.get().device()->fConfig); + } catch (...) { + return DefaultsHelpers::pipelineLength(0); + } + }; + static int detectedPipelineLength = getPipelineLengthHelper(); + pipelineLength = detectedPipelineLength; + } setPipelineLength(pipelineLength); } else { policy.configureRelayer(*this); @@ -173,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; } @@ -191,7 +203,7 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector gsl::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; @@ -200,30 +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{}; }; -#if __has_include() 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(); }; -#else - std::function refCountGetter = nullptr; -#endif - 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) { @@ -235,12 +248,14 @@ DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector(); @@ -346,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) { @@ -398,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); @@ -442,7 +457,8 @@ DataRelayer::RelayChoice InputInfo const& info, size_t nMessages, size_t nPayloads, - std::function&, TimesliceIndex::OldestOutputInfo)> onDrop) + OnInsertionCallback onInsertion, + OnDropCallback onDrop) { std::scoped_lock lock(mMutex); DataProcessingHeader const* dph = o2::header::get(rawHeader); @@ -488,6 +504,7 @@ DataRelayer::RelayChoice &messages, &nMessages, &nPayloads, + &onInsertion, &cache = mCache, &services = mContext, numInputTypes = mDistinctRoutesIndex.size()](TimesliceId timeslice, int input, TimesliceSlot slot, InputInfo const& info) -> size_t { @@ -497,12 +514,18 @@ 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 assert(nPayloads > 0); size_t saved = 0; + // It's guaranteed we will see all these messages only once, so we can + // do the forwarding here. + auto allMessages = std::span(messages, messages + nMessages); + if (onInsertion) { + onInsertion(services, allMessages); + } for (size_t mi = 0; mi < nMessages; ++mi) { assert(mi + nPayloads < nMessages); // We are in calibration mode and the data does not have the calibration bit set. @@ -518,7 +541,12 @@ DataRelayer::RelayChoice mi += nPayloads; continue; } - target.add([&messages, &mi](size_t i) -> fair::mq::MessagePtr& { return messages[mi + i]; }, nPayloads + 1); + 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. + for (size_t i = 0; i < nPayloads + 1; ++i) { + target.emplace_back(std::move(span[i])); + } mi += nPayloads; saved += nPayloads; } @@ -710,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) -> gsl::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; @@ -766,30 +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{}; }; -#if __has_include() 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(); }; -#else - std::function refCountGetter = nullptr; -#endif - 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); @@ -857,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; @@ -883,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); @@ -895,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); @@ -911,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; @@ -937,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})); } }; @@ -1065,7 +1095,7 @@ void DataRelayer::sendContextState() char* buffer = relayerSlotState + written; for (size_t ci = 0; ci < mTimesliceIndex.size(); ++ci) { for (size_t si = 0; si < mDistinctRoutesIndex.size(); ++si) { - int index = si * mTimesliceIndex.size() + ci; + int index = ci * mDistinctRoutesIndex.size() + si; int value = static_cast(mCachedStateMetrics[index]); buffer[si] = value + '0'; // Anything which is done is actually already empty, diff --git a/Framework/Core/src/DataSpecUtils.cxx b/Framework/Core/src/DataSpecUtils.cxx index 48f5e6abcad5b..bc1fcd180ed76 100644 --- a/Framework/Core/src/DataSpecUtils.cxx +++ b/Framework/Core/src/DataSpecUtils.cxx @@ -89,6 +89,11 @@ std::string DataSpecUtils::describe(OutputSpec const& spec) spec.matcher); } +std::string DataSpecUtils::describe(ConcreteDataMatcher const& matcher) +{ + return join(matcher, "/"); +} + template size_t DataSpecUtils::describe(char* buffer, size_t size, T const& spec) { @@ -664,16 +669,39 @@ InputSpec DataSpecUtils::fromMetadataString(std::string s) if (std::distance(words, std::sregex_iterator()) != 4) { throw runtime_error_f("Malformed input spec metadata: %s", s.c_str()); } - std::vector data; + std::array data; + auto pos = 0; for (auto i = words; i != std::sregex_iterator(); ++i) { - data.emplace_back(i->str()); + data[pos] = i->str(); + ++pos; } char origin[4]; char description[16]; std::memcpy(&origin, data[1].c_str(), 4); std::memcpy(&description, data[2].c_str(), 16); auto version = static_cast(std::atoi(data[3].c_str())); - return InputSpec{data[0], header::DataOrigin{origin}, header::DataDescription{description}, version, Lifetime::Timeframe}; + return {data[0], header::DataOrigin{origin}, header::DataDescription{description}, version, Lifetime::Timeframe}; +} + +ConcreteDataMatcher DataSpecUtils::fromString(std::string s) +{ + std::regex word_regex("(\\w+)"); + auto words = std::sregex_iterator(s.begin(), s.end(), word_regex); + if (std::distance(words, std::sregex_iterator()) != 3) { + throw runtime_error_f("Malformed serialized matcher: %s", s.c_str()); + } + std::array data; + auto pos = 0; + for (auto i = words; i != std::sregex_iterator(); ++i) { + data[pos] = i->str(); + ++pos; + } + char origin[4]; + char description[16]; + std::memcpy(&origin, data[0].c_str(), 4); + std::memcpy(&description, data[1].c_str(), 16); + auto version = static_cast(std::atoi(data[2].c_str())); + return {header::DataOrigin{origin}, header::DataDescription{description}, version}; } std::optional DataSpecUtils::getOptionalOrigin(InputSpec const& spec) diff --git a/Framework/Core/src/DecongestionService.h b/Framework/Core/src/DecongestionService.h index c45e9a36217ec..fe7fce8d640db 100644 --- a/Framework/Core/src/DecongestionService.h +++ b/Framework/Core/src/DecongestionService.h @@ -18,6 +18,8 @@ namespace o2::framework struct DecongestionService { /// Wether we are a source in the processing chain bool isFirstInTopology = true; + /// do not advertise/forward DomainInfoHeader from this device + bool suppressDomainInfo = false; /// The last timeslice which the ExpirationHandler::Creator callback /// created. This can be used to skip dummy iterations. size_t nextEnumerationTimeslice = 0; @@ -31,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/DefaultsHelpers.cxx b/Framework/Core/src/DefaultsHelpers.cxx index 4dcc734216f0c..5fd1ed29e7af6 100644 --- a/Framework/Core/src/DefaultsHelpers.cxx +++ b/Framework/Core/src/DefaultsHelpers.cxx @@ -11,6 +11,9 @@ #include "Framework/DefaultsHelpers.h" #include "Framework/DataTakingContext.h" +#include "Framework/DeviceConfig.h" +#include + #include #include #include @@ -18,23 +21,35 @@ namespace o2::framework { -unsigned int DefaultsHelpers::pipelineLength() +unsigned int DefaultsHelpers::pipelineLength(unsigned int minLength) { static bool override = getenv("DPL_DEFAULT_PIPELINE_LENGTH"); if (override) { static unsigned int retval = atoi(getenv("DPL_DEFAULT_PIPELINE_LENGTH")); - return retval; + return std::max(minLength, retval); } DeploymentMode deploymentMode = DefaultsHelpers::deploymentMode(); // just some reasonable numers // The number should really be tuned at runtime for each processor. if (deploymentMode == DeploymentMode::OnlineDDS || deploymentMode == DeploymentMode::OnlineECS || deploymentMode == DeploymentMode::FST) { - return 512; + return std::max(minLength, 512u); } else { - return 64; + return std::max(minLength, 64u); } } +unsigned int DefaultsHelpers::pipelineLength(const DeviceConfig& dc) +{ + static unsigned int minLength = dc.options.count("timeframes-rate-limit") ? std::max(0, atoi(dc.options["timeframes-rate-limit"].as().c_str())) : 0; + return pipelineLength(minLength); +} + +unsigned int DefaultsHelpers::pipelineLength(const fair::mq::ProgOptions& options) +{ + static unsigned int minLength = options.Count("timeframes-rate-limit") ? std::max(0, atoi(options.GetValue("timeframes-rate-limit").c_str())) : 0; + return pipelineLength(minLength); +} + static DeploymentMode getDeploymentMode_internal() { char* explicitMode = getenv("O2_DPL_DEPLOYMENT_MODE"); diff --git a/Framework/Core/src/DeviceSpecHelpers.cxx b/Framework/Core/src/DeviceSpecHelpers.cxx index ec0a40e44ac31..011b3aa12162f 100644 --- a/Framework/Core/src/DeviceSpecHelpers.cxx +++ b/Framework/Core/src/DeviceSpecHelpers.cxx @@ -1541,6 +1541,7 @@ void DeviceSpecHelpers::prepareArguments(bool defaultQuiet, bool defaultStopped, realOdesc.add_options()("child-driver", bpo::value()); realOdesc.add_options()("rate", bpo::value()); realOdesc.add_options()("exit-transition-timeout", bpo::value()); + realOdesc.add_options()("error-on-exit-transition-timeout", bpo::value()->zero_tokens()); realOdesc.add_options()("data-processing-timeout", bpo::value()); realOdesc.add_options()("expected-region-callbacks", bpo::value()); realOdesc.add_options()("timeframes-rate-limit", bpo::value()); @@ -1728,9 +1729,10 @@ boost::program_options::options_description DeviceSpecHelpers::getForwardedDevic ("control-port", bpo::value(), "Utility port to be used by O2 Control") // ("rate", bpo::value(), "rate for a data source device (Hz)") // ("exit-transition-timeout", bpo::value(), "timeout before switching to READY state") // + ("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/ExpressionJSONHelpers.cxx b/Framework/Core/src/ExpressionJSONHelpers.cxx new file mode 100644 index 0000000000000..a6e19875381cd --- /dev/null +++ b/Framework/Core/src/ExpressionJSONHelpers.cxx @@ -0,0 +1,875 @@ +// Copyright 2019-2025 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 "ExpressionJSONHelpers.h" + +#include +#include +#include +#include +#include + +#include +#include +#include "Framework/VariantHelpers.h" + +namespace o2::framework +{ +namespace +{ +using nodes = expressions::Node::self_t; +enum struct Nodes : int { + NLITERAL = 0, + NBINDING = 1, + NOP = 2, + NNPH = 3, + NCOND = 4, + NPAR = 5 +}; + +enum struct ToWrite { + FULL, + LEFT, + RIGHT, + COND, + POP +}; + +struct Entry { + expressions::Node* ptr = nullptr; + ToWrite toWrite = ToWrite::FULL; +}; + +std::array validKeys{ + "projectors", + "kind", + "binding", + "index", + "arrow_type", + "value", + "hash", + "operation", + "left", + "right", + "condition"}; + +struct ExpressionReader : public rapidjson::BaseReaderHandler, ExpressionReader> { + using Ch = rapidjson::UTF8<>::Ch; + using SizeType = rapidjson::SizeType; + + enum struct State { + IN_START, // global start + IN_LIST, // opening brace of the list + IN_ROOT, // after encountering the opening of the expression object + IN_LEFT, // in "left" key - subexpression + IN_RIGHT, // in "right" key - subexpression + IN_COND, // in "condition" key - subexpression + IN_NODE_LITERAL, // in literal node + IN_NODE_BINDING, // in binding node + IN_NODE_OP, // in operation node + IN_NODE_CONDITIONAL, // in conditional node + IN_ERROR // generic error state + }; + + std::stack states; + std::stack path; + std::ostringstream debug; + + std::vector result; + + std::unique_ptr rootNode = nullptr; + std::unique_ptr node = nullptr; + expressions::LiteralValue::stored_type value; + atype::type type; + Nodes kind; + std::string binding; + BasicOp operation; + uint32_t hash; + size_t index; + + std::string previousKey; + std::string currentKey; + + ExpressionReader() + { + debug << ">>> Start" << std::endl; + states.push(State::IN_START); + } + + bool StartArray() + { + debug << "StartArray()" << std::endl; + if (states.top() == State::IN_START) { + states.push(State::IN_LIST); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool EndArray(SizeType) + { + debug << "EndArray()" << std::endl; + if (states.top() == State::IN_LIST) { + states.pop(); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool Key(const Ch* str, SizeType, bool) + { + debug << "Key(" << str << ")" << std::endl; + previousKey = currentKey; + currentKey = str; + if (std::find(validKeys.begin(), validKeys.end(), currentKey) == validKeys.end()) { + states.push(State::IN_ERROR); + return false; + } + + if (states.top() == State::IN_START) { + if (currentKey.compare("projectors") == 0) { + return true; + } + } + + if (states.top() == State::IN_ROOT) { + if (currentKey.compare("kind") == 0) { + return true; + } else { + states.push(State::IN_ERROR); // should start from root node + return false; + } + } + + if (states.top() == State::IN_LEFT || states.top() == State::IN_RIGHT || states.top() == State::IN_COND) { + if (currentKey.compare("kind") == 0) { + return true; + } + } + + if (states.top() == State::IN_NODE_LITERAL || states.top() == State::IN_NODE_OP || states.top() == State::IN_NODE_BINDING || states.top() == State::IN_NODE_CONDITIONAL) { + if (currentKey.compare("index") == 0) { + return true; + } + if (currentKey.compare("left") == 0) { + // this is the point where the node header is parsed and we can create it + // create a new node instance here and set a pointer to it in a parent (current stack top), based on its state + // push the new node into the stack with LEFT state + switch (states.top()) { + case State::IN_NODE_LITERAL: + node = std::make_unique(expressions::LiteralNode{value, type}); + break; + case State::IN_NODE_BINDING: + node = std::make_unique(expressions::BindingNode{hash, type}, binding); + break; + case State::IN_NODE_OP: + node = std::make_unique(expressions::OpNode{operation}, expressions::LiteralNode{-1}); + break; + case State::IN_NODE_CONDITIONAL: + node = std::make_unique(expressions::ConditionalNode{}, expressions::LiteralNode{-1}, expressions::LiteralNode{-1}, expressions::LiteralNode{true}); + break; + default: + states.push(State::IN_ERROR); + return false; + } + + if (path.empty()) { + rootNode = std::move(node); + path.emplace(rootNode.get(), ToWrite::LEFT); + } else { + auto* n = path.top().ptr; + switch (path.top().toWrite) { + case ToWrite::LEFT: + n->left = std::move(node); + path.top().toWrite = ToWrite::RIGHT; + path.emplace(n->left.get(), ToWrite::LEFT); + break; + case ToWrite::RIGHT: + n->right = std::move(node); + path.top().toWrite = ToWrite::COND; + path.emplace(n->right.get(), ToWrite::LEFT); + break; + case ToWrite::COND: + n->condition = std::move(node); + path.pop(); + path.emplace(n->condition.get(), ToWrite::LEFT); + break; + default: + states.push(State::IN_ERROR); + return false; + } + } + + states.push(State::IN_LEFT); + return true; + } + if (currentKey.compare("right") == 0) { + if (states.top() == State::IN_LEFT) { + states.pop(); + } + // move the stack state of the node to RIGHT state + path.top().toWrite = ToWrite::RIGHT; + states.push(State::IN_RIGHT); + return true; + } + if (currentKey.compare("condition") == 0) { + if (states.top() == State::IN_RIGHT) { + states.pop(); + } + // move the stack state of the node to COND state + path.top().toWrite = ToWrite::COND; + states.push(State::IN_COND); + return true; + } + } + + if (states.top() == State::IN_NODE_LITERAL) { + if (currentKey.compare("arrow_type") == 0 || currentKey.compare("value") == 0) { + return true; + } + } + + if (states.top() == State::IN_NODE_BINDING) { + if (currentKey.compare("binding") == 0 || currentKey.compare("hash") == 0 || currentKey.compare("arrow_type") == 0) { + return true; + } + } + + if (states.top() == State::IN_NODE_OP) { + if (currentKey.compare("operation") == 0) { + return true; + } + } + + debug << ">>> Unrecognized" << std::endl; + states.push(State::IN_ERROR); + return false; + } + + bool StartObject() + { + // opening brace encountered + debug << "StartObject()" << std::endl; + // the first opening brace in the input + if (states.top() == State::IN_START) { + return true; + } + // the opening of an expression + if (states.top() == State::IN_LIST) { + states.push(State::IN_ROOT); + return true; + } + // if we are looking at subexpression + if (states.top() == State::IN_LEFT || states.top() == State::IN_RIGHT || states.top() == State::IN_COND) { // ready to start a new node + return true; + } + // no other object starts are expected + states.push(State::IN_ERROR); + return false; + } + + bool EndObject(SizeType) + { + // closing brace encountered + debug << "EndObject()" << std::endl; + // we are closing up an expression + if (states.top() == State::IN_NODE_LITERAL || states.top() == State::IN_NODE_OP || states.top() == State::IN_NODE_BINDING || states.top() == State::IN_NODE_CONDITIONAL) { // finalize node + // finalize the current node and pop it from the stack (the pointers should be already set + states.pop(); + // subexpression + if (states.top() == State::IN_LEFT || states.top() == State::IN_RIGHT || states.top() == State::IN_COND) { + states.pop(); + return true; + } + + // expression + if (states.top() == State::IN_ROOT) { + result.emplace_back(std::move(rootNode)); + states.pop(); + return true; + } + } + + // we are closing the list + if (states.top() == State::IN_START) { + return true; + } + // no other object ends are expectedd + states.push(State::IN_ERROR); + return false; + } + + bool Null() + { + // null value + debug << "Null()" << std::endl; + // the subexpression can be empty + if (states.top() == State::IN_LEFT || states.top() == State::IN_RIGHT || states.top() == State::IN_COND) { + // empty node, nothing to do + // move the path state to the next + if (path.top().toWrite == ToWrite::LEFT) { + path.top().toWrite = ToWrite::RIGHT; + } else if (path.top().toWrite == ToWrite::RIGHT) { + path.top().toWrite = ToWrite::COND; + } else if (path.top().toWrite == ToWrite::COND) { + path.pop(); + } + + states.pop(); + return true; + } + states.push(State::IN_ERROR); // no other contexts allow null + return false; + } + + bool Bool(bool b) + { + debug << "Bool(" << b << ")" << std::endl; + // can be a value in a literal node + if (states.top() == State::IN_NODE_LITERAL && currentKey.compare("value") == 0) { + value = b; + return true; + } + states.push(State::IN_ERROR); // no other contexts allow booleans + return false; + } + + bool Int(int i) + { + debug << "Int(" << i << ")" << std::endl; + // can be a value in a literal node + if (states.top() == State::IN_NODE_LITERAL && currentKey.compare("value") == 0) { // literal + switch (type) { + case atype::INT8: + value = (int8_t)i; + break; + case atype::INT16: + value = (int16_t)i; + break; + case atype::INT32: + value = i; + break; + case atype::UINT8: + value = (uint8_t)i; + break; + case atype::UINT16: + value = (uint16_t)i; + break; + case atype::UINT32: + value = (uint32_t)i; + break; + default: + states.push(State::IN_ERROR); + return false; + } + return true; + } + // can be a node kind designator + if (states.top() == State::IN_ROOT || states.top() == State::IN_LEFT || states.top() == State::IN_RIGHT || states.top() == State::IN_COND) { + if (currentKey.compare("kind") == 0) { + kind = (Nodes)i; + switch (kind) { + case Nodes::NLITERAL: + case Nodes::NNPH: + case Nodes::NPAR: { + states.push(State::IN_NODE_LITERAL); + debug << ">>> Literal node" << std::endl; + return true; + } + case Nodes::NBINDING: { + states.push(State::IN_NODE_BINDING); + debug << ">>> Binding node" << std::endl; + return true; + } + case Nodes::NOP: { + states.push(State::IN_NODE_OP); + debug << ">>> Operation node" << std::endl; + return true; + } + case Nodes::NCOND: { + states.push(State::IN_NODE_CONDITIONAL); + debug << ">>> Conditional node" << std::endl; + return true; + } + } + } + } + // can be node index + if (states.top() == State::IN_NODE_BINDING || states.top() == State::IN_NODE_CONDITIONAL || states.top() == State::IN_NODE_LITERAL || states.top() == State::IN_NODE_OP) { + if (currentKey.compare("index") == 0) { + index = (size_t)i; + return true; + } + } + // can be a node type designator + if (states.top() == State::IN_NODE_LITERAL || states.top() == State::IN_NODE_BINDING) { + if (currentKey.compare("arrow_type") == 0) { + type = (atype::type)i; + return true; + } + } + // can be a node operation designato + if (states.top() == State::IN_NODE_OP && currentKey.compare("operation") == 0) { + operation = (BasicOp)i; + return true; + } + states.push(State::IN_ERROR); // no other contexts allow ints + return false; + } + + bool Uint(unsigned i) + { + debug << "Uint(" << i << ")" << std::endl; + // can be node hash + if (states.top() == State::IN_NODE_BINDING && currentKey.compare("hash") == 0) { + hash = i; + return true; + } + // any positive value will be first read as unsigned, however the actual type is determined by node's arrow_type + debug << ">> falling back to Int" << std::endl; + return Int(i); + } + + bool Int64(int64_t i) + { + debug << "Int64(" << i << ")" << std::endl; + // can only be a literal node value + if (states.top() == State::IN_NODE_LITERAL && currentKey.compare("value") == 0) { + switch (type) { + case atype::UINT64: + value = (uint64_t)i; + break; + case atype::INT64: + value = (int64_t)i; + break; + default: + states.push(State::IN_ERROR); + return false; + } + return true; + } + states.push(State::IN_ERROR); // no other contexts allow int64s + return false; + } + + bool Uint64(uint64_t i) + { + debug << "Uint64(" << i << ")" << std::endl; + // any positive value will be first read as unsigned, however the actual type is determined by node's arrow_type + debug << ">> falling back to Int64" << std::endl; + return Int64(i); + } + + bool Double(double d) + { + debug << "Double(" << d << ")" << std::endl; + // can only be a literal node value + if (states.top() == State::IN_NODE_LITERAL) { + switch (type) { + case atype::FLOAT: + value = (float)d; + break; + case atype::DOUBLE: + value = d; + break; + default: + states.push(State::IN_ERROR); + return false; + } + return true; + } + states.push(State::IN_ERROR); // no other contexts allow doubles + return false; + } + + bool String(const Ch* str, SizeType, bool) + { + debug << "String(" << str << ")" << std::endl; + // can only be a binding node + if (states.top() == State::IN_NODE_BINDING && currentKey.compare("binding") == 0) { + binding = str; + return true; + } + states.push(State::IN_ERROR); // no strings are expected + return false; + } +}; +} // namespace + +std::vector o2::framework::ExpressionJSONHelpers::read(std::istream& s) +{ + rapidjson::Reader reader; + rapidjson::IStreamWrapper isw(s); + ExpressionReader ereader; + bool ok = reader.Parse(isw, ereader); + + if (!ok) { + throw framework::runtime_error_f("Cannot parse serialized Expression, error: %s at offset: %d", rapidjson::GetParseError_En(reader.GetParseErrorCode()), reader.GetErrorOffset()); + } + return std::move(ereader.result); +} + +namespace +{ +void writeNodeHeader(rapidjson::Writer& w, expressions::Node const* node) +{ + w.Key("kind"); + w.Int((int)node->self.index()); + w.Key("index"); + w.Uint64(node->index); + std::visit(overloaded{ + [&w](expressions::LiteralNode const& node) { + w.Key("arrow_type"); + w.Int(node.type); + w.Key("value"); + std::visit(overloaded{ + [&w](bool v) { w.Bool(v); }, + [&w](float v) { w.Double(v); }, + [&w](double v) { w.Double(v); }, + [&w](uint8_t v) { w.Uint(v); }, + [&w](uint16_t v) { w.Uint(v); }, + [&w](uint32_t v) { w.Uint(v); }, + [&w](uint64_t v) { w.Uint64(v); }, + [&w](int8_t v) { w.Int(v); }, + [&w](int16_t v) { w.Int(v); }, + [&w](int v) { w.Int(v); }, + [&w](int64_t v) { w.Int64(v); }}, + node.value); + }, + [&w](expressions::BindingNode const& node) { + w.Key("binding"); + w.String(node.name); + w.Key("hash"); + w.Uint(node.hash); + w.Key("arrow_type"); + w.Int(node.type); + }, + [&w](expressions::OpNode const& node) { + w.Key("operation"); + w.Int(node.op); + }, + [](expressions::ConditionalNode const&) { + }}, + node->self); +} + +void writeExpression(rapidjson::Writer& w, expressions::Node* n) +{ + std::stack path; + path.emplace(n, ToWrite::FULL); + while (!path.empty()) { + auto& top = path.top(); + + if (top.toWrite == ToWrite::FULL) { + w.StartObject(); + writeNodeHeader(w, top.ptr); + top.toWrite = ToWrite::LEFT; + continue; + } + + if (top.toWrite == ToWrite::LEFT) { + w.Key("left"); + top.toWrite = ToWrite::RIGHT; + auto* left = top.ptr->left.get(); + if (left != nullptr) { + path.emplace(left, ToWrite::FULL); + } else { + w.Null(); + } + continue; + } + + if (top.toWrite == ToWrite::RIGHT) { + w.Key("right"); + top.toWrite = ToWrite::COND; + auto* right = top.ptr->right.get(); + if (right != nullptr) { + path.emplace(right, ToWrite::FULL); + } else { + w.Null(); + } + continue; + } + + if (top.toWrite == ToWrite::COND) { + w.Key("condition"); + top.toWrite = ToWrite::POP; + auto* cond = top.ptr->condition.get(); + if (cond != nullptr) { + path.emplace(cond, ToWrite::FULL); + } else { + w.Null(); + } + continue; + } + + if (top.toWrite == ToWrite::POP) { + w.EndObject(); + path.pop(); + continue; + } + } +} +} // namespace + +void o2::framework::ExpressionJSONHelpers::write(std::ostream& o, std::vector& projectors) +{ + rapidjson::OStreamWrapper osw(o); + rapidjson::Writer w(osw); + w.StartObject(); + w.Key("projectors"); + w.StartArray(); + for (auto& p : projectors) { + writeExpression(w, p.node.get()); + } + w.EndArray(); + w.EndObject(); +} + +namespace +{ +std::shared_ptr arrowDataTypeFromId(atype::type type, int list_size = 1, atype::type element = atype::NA) +{ + switch (list_size) { + case -1: + return arrow::list(expressions::concreteArrowType(element)); + case 1: + return expressions::concreteArrowType(type); + default: + return arrow::fixed_size_list(expressions::concreteArrowType(element), list_size); + } +} + +struct SchemaReader : public rapidjson::BaseReaderHandler, SchemaReader> { + using Ch = rapidjson::UTF8<>::Ch; + using SizeType = rapidjson::SizeType; + + enum struct State { + IN_START, + IN_LIST, + IN_FIELD, + IN_ERROR + }; + + std::stack states; + std::ostringstream debug; + + std::shared_ptr schema = nullptr; + std::vector> fields; + + std::string currentKey; + + std::string name; + atype::type type; + atype::type element; + int list_size = 1; + + SchemaReader() + { + debug << ">>> Start" << std::endl; + states.push(State::IN_START); + } + + bool StartArray() + { + debug << "StartArray()" << std::endl; + if (states.top() == State::IN_START && currentKey.compare("fields") == 0) { + states.push(State::IN_LIST); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool EndArray(SizeType) + { + debug << "EndArray()" << std::endl; + if (states.top() == State::IN_LIST) { + // finalize schema + schema = std::make_shared(fields); + states.pop(); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool Key(const Ch* str, SizeType, bool) + { + debug << "Key(" << str << ")" << std::endl; + currentKey = str; + if (states.top() == State::IN_START) { + if (currentKey.compare("fields") == 0) { + return true; + } + } + + if (states.top() == State::IN_FIELD) { + if (currentKey.compare("name") == 0) { + return true; + } + if (currentKey.compare("type") == 0) { + return true; + } + if (currentKey.compare("size") == 0) { + return true; + } + if (currentKey.compare("element") == 0) { + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } + + bool StartObject() + { + debug << "StartObject()" << std::endl; + if (states.top() == State::IN_START) { + return true; + } + + if (states.top() == State::IN_LIST) { + states.push(State::IN_FIELD); + list_size = 1; + element = atype::NA; + type = atype::NA; + return true; + } + + states.push(State::IN_ERROR); + return false; + } + + bool EndObject(SizeType) + { + debug << "EndObject()" << std::endl; + if (states.top() == State::IN_FIELD) { + states.pop(); + // add a field + fields.emplace_back(std::make_shared(name, arrowDataTypeFromId(type, list_size, element))); + return true; + } + + if (states.top() == State::IN_START) { + return true; + } + + states.push(State::IN_ERROR); + return false; + } + + bool Uint(unsigned i) + { + debug << "Uint(" << i << ")" << std::endl; + if (states.top() == State::IN_FIELD) { + if (currentKey.compare("type") == 0) { + type = (atype::type)i; + return true; + } + if (currentKey.compare("element") == 0) { + element = (atype::type)i; + return true; + } + if (currentKey.compare("size") == 0) { + list_size = i; + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } + + bool String(const Ch* str, SizeType, bool) + { + debug << "String(" << str << ")" << std::endl; + if (states.top() == State::IN_FIELD) { + if (currentKey.compare("name") == 0) { + name = str; + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } + + bool Int(int i) + { + debug << "Int(" << i << ")" << std::endl; + if (states.top() == State::IN_FIELD && currentKey.compare("size") == 0) { + list_size = i; + return true; + } + return Uint(i); + } +}; +} // namespace + +std::shared_ptr o2::framework::ArrowJSONHelpers::read(std::istream& s) +{ + rapidjson::Reader reader; + rapidjson::IStreamWrapper isw(s); + SchemaReader sreader; + + bool ok = reader.Parse(isw, sreader); + + if (!ok) { + throw framework::runtime_error_f("Cannot parse serialized Schema, error: %s at offset: %d", rapidjson::GetParseError_En(reader.GetParseErrorCode()), reader.GetErrorOffset()); + } + return sreader.schema; +} + +namespace +{ +void writeSchema(rapidjson::Writer& w, arrow::Schema* schema) +{ + for (auto& f : schema->fields()) { + w.StartObject(); + w.Key("name"); + w.String(f->name().c_str()); + auto fixedList = dynamic_cast(f->type().get()); + if (fixedList != nullptr) { + w.Key("size"); + w.Int(fixedList->list_size()); + w.Key("element"); + w.Int(fixedList->field(0)->type()->id()); + } + auto varList = dynamic_cast(f->type().get()); + if (varList != nullptr) { + w.Key("size"); + w.Int(-1); + w.Key("element"); + w.Int(varList->field(0)->type()->id()); + } + w.Key("type"); + w.Int(f->type()->id()); + w.EndObject(); + } +} +} // namespace + +void o2::framework::ArrowJSONHelpers::write(std::ostream& o, std::shared_ptr& schema) +{ + rapidjson::OStreamWrapper osw(o); + rapidjson::Writer w(osw); + w.StartObject(); + w.Key("fields"); + w.StartArray(); + writeSchema(w, schema.get()); + w.EndArray(); + w.EndObject(); +} + +} // namespace o2::framework diff --git a/Framework/Core/src/ExpressionJSONHelpers.h b/Framework/Core/src/ExpressionJSONHelpers.h new file mode 100644 index 0000000000000..ed4c51c58d5c2 --- /dev/null +++ b/Framework/Core/src/ExpressionJSONHelpers.h @@ -0,0 +1,29 @@ +// Copyright 2019-2025 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_EXPRESSIONJSONHELPERS_H +#define FRAMEWORK_EXPRESSIONJSONHELPERS_H + +#include "Framework/Expressions.h" + +namespace o2::framework +{ +struct ExpressionJSONHelpers { + static std::vector read(std::istream& s); + static void write(std::ostream& o, std::vector& projectors); +}; + +struct ArrowJSONHelpers { + static std::shared_ptr read(std::istream& s); + static void write(std::ostream& o, std::shared_ptr& schema); +}; +} // namespace o2::framework + +#endif // FRAMEWORK_EXPRESSIONJSONHELPERS_H diff --git a/Framework/Core/src/Expressions.cxx b/Framework/Core/src/Expressions.cxx index 05a3462d6e4da..02a862d30032b 100644 --- a/Framework/Core/src/Expressions.cxx +++ b/Framework/Core/src/Expressions.cxx @@ -1348,4 +1348,20 @@ OpNode Parser::opFromToken(std::string const& token) return OpNode{static_cast(std::distance(mapping.begin(), locate))}; } +std::vector> materializeProjectors(std::vector const& projectors, std::shared_ptr const& inputSchema, std::vector> const& outputFields) +{ + std::vector> expressions; + int i = 0; + for (auto const& p : projectors) { + expressions.push_back( + expressions::makeExpression( + expressions::createExpressionTree( + expressions::createOperations(p), + inputSchema), + outputFields[i])); + ++i; + } + return expressions; +} + } // namespace o2::framework::expressions diff --git a/Framework/Core/src/ExternalFairMQDeviceProxy.cxx b/Framework/Core/src/ExternalFairMQDeviceProxy.cxx index c07baeaaf40b8..d4ee776986184 100644 --- a/Framework/Core/src/ExternalFairMQDeviceProxy.cxx +++ b/Framework/Core/src/ExternalFairMQDeviceProxy.cxx @@ -32,6 +32,7 @@ #include "Framework/DeviceState.h" #include "Framework/Monitoring.h" #include "Framework/SendingPolicy.h" +#include "Framework/DataProcessingHelpers.h" #include "Headers/DataHeader.h" #include "Headers/Stack.h" #include "DecongestionService.h" @@ -864,6 +865,10 @@ DataProcessorSpec specifyExternalFairMQDeviceProxy(char const* name, bool didSendParts = false; for (size_t ci = 0; ci < channels.size(); ++ci) { + // check for state transition request every 10th input channel to avoid large delays of EoS timers + if (ci > 0 && ci % 10 == 0) { + ctx.services().get().transitionHandling = DataProcessingHelpers::updateStateTransition(ctx.services(), ctx.services().get().processingPolicies); + } std::string const& channel = channels[ci]; int waitTime = channels.size() == 1 ? -1 : 1; int maxRead = 1000; @@ -1000,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; @@ -1022,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? @@ -1040,6 +1045,7 @@ DataProcessorSpec specifyFairMQDeviceOutputProxy(char const* name, spec.options = { ConfigParamSpec{"channel-config", VariantType::String, d, {"Out-of-band channel config"}}, }; + spec.labels.push_back(DataProcessorLabel{"output-proxy"}); return spec; } @@ -1085,7 +1091,18 @@ DataProcessorSpec specifyFairMQDeviceMultiOutputProxy(char const* name, channelNames->emplace_back(std::move(channel)); } - proxy.bind(mutableDeviceSpec.outputs, mutableDeviceSpec.inputs, mutableDeviceSpec.forwards, *device); + std::function bindByName = [device](std::string const& channelName) -> fair::mq::Channel& { + auto channel = device->GetChannels().find(channelName); + if (channel == device->GetChannels().end()) { + LOGP(fatal, "Expected channel {} not configured.", channelName); + } + return channel->second.at(0); + }; + + std::function newStateCallback = [device]() -> bool { + return device->NewStatePending(); + }; + proxy.bind(mutableDeviceSpec.outputs, mutableDeviceSpec.inputs, mutableDeviceSpec.forwards, bindByName, newStateCallback); }; // We need to clear the channels on stop, because we will check and add them auto channelConfigurationDisposer = [&deviceSpec]() { @@ -1120,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; @@ -1146,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? @@ -1164,6 +1181,7 @@ DataProcessorSpec specifyFairMQDeviceMultiOutputProxy(char const* name, spec.options = { ConfigParamSpec{"channel-config", VariantType::String, d, {"Out-of-band channel config"}}, }; + spec.labels.push_back(DataProcessorLabel{"output-proxy"}); return spec; } diff --git a/Framework/Core/src/FairMQDeviceProxy.cxx b/Framework/Core/src/FairMQDeviceProxy.cxx index bdffddd5a4d1a..e121084b866a2 100644 --- a/Framework/Core/src/FairMQDeviceProxy.cxx +++ b/Framework/Core/src/FairMQDeviceProxy.cxx @@ -230,7 +230,8 @@ std::unique_ptr FairMQDeviceProxy::createForwardMessage(Route void FairMQDeviceProxy::bind(std::vector const& outputs, std::vector const& inputs, std::vector const& forwards, - fair::mq::Device& device) + std::function bindChannelByName, + std::function newStatePending) { mOutputs.clear(); mOutputRoutes.clear(); @@ -258,14 +259,11 @@ void FairMQDeviceProxy::bind(std::vector const& outputs, std::vecto if (channelPos == channelNameToChannel.end()) { channelIndex = ChannelIndex{(int)mOutputChannelInfos.size()}; ChannelAccountingType dplChannel = (route.channel.rfind("from_", 0) == 0) ? ChannelAccountingType::DPL : ChannelAccountingType::RAWFMQ; - auto channel = device.GetChannels().find(route.channel); - if (channel == device.GetChannels().end()) { - LOGP(fatal, "Expected channel {} not configured.", route.channel); - } + auto& channel = bindChannelByName(route.channel); OutputChannelInfo info{ .name = route.channel, .channelType = dplChannel, - .channel = channel->second.at(0), + .channel = channel, .policy = route.policy, .index = channelIndex, }; @@ -305,11 +303,9 @@ void FairMQDeviceProxy::bind(std::vector const& outputs, std::vecto if (channelPos == channelNameToChannel.end()) { channelIndex = ChannelIndex{(int)mInputChannels.size()}; - auto channel = device.GetChannels().find(route.sourceChannel); - if (channel == device.GetChannels().end()) { - LOGP(fatal, "Expected channel {} not configured.", route.sourceChannel); - } - mInputChannels.push_back(&channel->second.at(0)); + fair::mq::Channel& channel = bindChannelByName(route.sourceChannel); + + mInputChannels.push_back(&channel); mInputChannelNames.push_back(route.sourceChannel); channelNameToChannel[route.sourceChannel] = channelIndex; LOGP(detail, "Binding channel {} to channel index {}", route.sourceChannel, channelIndex.value); @@ -341,12 +337,10 @@ void FairMQDeviceProxy::bind(std::vector const& outputs, std::vecto if (channelPos == channelNameToChannel.end()) { channelIndex = ChannelIndex{(int)mForwardChannelInfos.size()}; - auto channel = device.GetChannels().find(route.channel); - if (channel == device.GetChannels().end()) { - LOGP(fatal, "Expected channel {} not configured.", route.channel); - } + auto& channel = bindChannelByName(route.channel); + ChannelAccountingType dplChannel = (route.channel.rfind("from_", 0) == 0) ? ChannelAccountingType::DPL : ChannelAccountingType::RAWFMQ; - mForwardChannelInfos.push_back(ForwardChannelInfo{.name = route.channel, .channelType = dplChannel, .channel = channel->second.at(0), .policy = route.policy, .index = channelIndex}); + mForwardChannelInfos.push_back(ForwardChannelInfo{.name = route.channel, .channelType = dplChannel, .channel = channel, .policy = route.policy, .index = channelIndex}); mForwardChannelStates.push_back(ForwardChannelState{0}); channelNameToChannel[route.channel] = channelIndex; LOGP(detail, "Binding forward channel {} to channel index {}", route.channel, channelIndex.value); @@ -368,6 +362,6 @@ void FairMQDeviceProxy::bind(std::vector const& outputs, std::vecto LOGP(detail, "Forward route {}@{}%{} to index {} and channelIndex {}", DataSpecUtils::describe(route.matcher), route.timeslice, route.maxTimeslices, fi, state.channel.value); } } - mStateChangeCallback = [&device]() -> bool { return device.NewStatePending(); }; + mStateChangeCallback = newStatePending; } } // namespace o2::framework diff --git a/Framework/Core/src/FragmentToBatch.cxx b/Framework/Core/src/FragmentToBatch.cxx new file mode 100644 index 0000000000000..ada31be814fc8 --- /dev/null +++ b/Framework/Core/src/FragmentToBatch.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2025 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/FragmentToBatch.h" +#include "Framework/Logger.h" +#include "Framework/Signpost.h" + +#include +#include +#include +#include +#include + +#include +#include + +O2_DECLARE_DYNAMIC_LOG(tabletree_helpers); + +namespace o2::framework +{ + +FragmentToBatch::FragmentToBatch(StreamerCreator creator, std::shared_ptr fragment, arrow::MemoryPool* pool) + : mFragment{std::move(fragment)}, + mArrowMemoryPool{pool}, + mCreator{std::move(creator)} +{ +} + +void FragmentToBatch::setLabel(const char* label) +{ + mTableLabel = label; +} + +void FragmentToBatch::fill(std::shared_ptr schema, std::shared_ptr format) +{ + auto options = std::make_shared(); + options->dataset_schema = schema; + auto scanner = format->ScanBatchesAsync(options, mFragment); + auto batch = (*scanner)(); + 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. +} + +std::shared_ptr FragmentToBatch::finalize() +{ + return mRecordBatch; +} + +} // namespace o2::framework diff --git a/Framework/Core/src/HTTPParser.cxx b/Framework/Core/src/HTTPParser.cxx index 04ca6e8fdce55..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); @@ -214,15 +214,16 @@ std::string HTTPParserHelpers::calculateAccept(const char* nonce) return fmt::format("{}", base); } -std::string encode_websocket_handshake_reply(char const* nonce) +std::string encode_websocket_handshake_reply(char const* nonce, const char* protocol) { constexpr auto res = "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Access-Control-Allow-Origin: \"*\"\r\n" + "{}" "Sec-WebSocket-Accept: {}\r\n\r\n"; - return fmt::format(res, HTTPParserHelpers::calculateAccept(nonce)); + return fmt::format(res, protocol && protocol[0] ? fmt::format("Sec-WebSocket-Protocol: {}\r\n", protocol) : "", HTTPParserHelpers::calculateAccept(nonce)); } void parse_http_request(char* start, size_t size, HTTPParser* parser) diff --git a/Framework/Core/src/HTTPParser.h b/Framework/Core/src/HTTPParser.h index b4d92393ca5c9..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; }; @@ -125,7 +137,8 @@ std::string encode_websocket_handshake_request(const char* path, const char* pro /// Encodes the server reply for a given websocket connection /// @a nonce the nonce of the request. -std::string encode_websocket_handshake_reply(char const* nonce); +/// @a protocol the websocket subprotocol to confirm (optional) +std::string encode_websocket_handshake_reply(char const* nonce, char const* protocol = ""); /// Encodes the buffer @a src which is @a size long to a number of buffers suitable to be sent via libuv. /// If @a binary is provided the binary bit is set. @@ -137,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) {} @@ -204,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/HistogramRegistry.cxx b/Framework/Core/src/HistogramRegistry.cxx index 0a0cc1fc3a690..9caa7cbd1f48e 100644 --- a/Framework/Core/src/HistogramRegistry.cxx +++ b/Framework/Core/src/HistogramRegistry.cxx @@ -51,9 +51,13 @@ OutputSpec const HistogramRegistry::spec() return OutputSpec{OutputLabel{mName}, "ATSK", desc, 0, Lifetime::QA}; } -OutputRef HistogramRegistry::ref(uint16_t pipelineIndex, uint16_t pipelineSize) +OutputRef HistogramRegistry::ref(uint16_t pipelineIndex, uint16_t pipelineSize) const { - return OutputRef{std::string{mName}, 0, o2::header::Stack{OutputObjHeader{mPolicy, OutputObjSourceType::HistogramRegistrySource, mTaskHash, pipelineIndex, pipelineSize}}}; + OutputObjHeader header{mPolicy, OutputObjSourceType::HistogramRegistrySource, mTaskHash, pipelineIndex, pipelineSize}; + // Copy the name of the registry to the haeder. + strncpy(header.containerName, mName.data(), OutputObjHeader::MAX_REGISTRY_NAME_SIZE); + header.createContainer = mCreateRegistryDir ? 1 : 0; + return OutputRef{std::string{mName}, 0, o2::header::Stack{header}}; } void HistogramRegistry::setHash(uint32_t hash) @@ -282,87 +286,26 @@ void HistogramRegistry::print(bool showAxisDetails) LOGF(info, ""); } -// create output structure will be propagated to file-sink -TList* HistogramRegistry::getListOfHistograms() +void HistogramRegistry::apply(std::function callback) const { - TList* list = new TList(); - list->SetName(mName.data()); - + // Keep the list sorted as originally done to avoid hidden dependency on the order, for now , for now. + auto finalList = mRegisteredNames; + auto caseInsensitiveCompare = [](const std::string& s1, const std::string& s2) { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(static_cast(c1)) < std::tolower(static_cast(c2)); }); + }; if (mSortHistos) { - auto caseInsensitiveCompare = [](const std::string& s1, const std::string& s2) { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return std::tolower(static_cast(c1)) < std::tolower(static_cast(c2)); }); - }; - std::sort(mRegisteredNames.begin(), mRegisteredNames.end(), caseInsensitiveCompare); + std::sort(finalList.begin(), finalList.end(), caseInsensitiveCompare); } - - for (auto& curHistName : mRegisteredNames) { + for (auto& curHistName : finalList) { TNamed* rawPtr = nullptr; std::visit([&](const auto& sharedPtr) { rawPtr = (TNamed*)sharedPtr.get(); }, mRegistryValue[getHistIndex(HistName{curHistName.data()})]); - if (rawPtr) { - std::deque path = splitPath(rawPtr->GetName()); - std::string name = path.back(); - path.pop_back(); - TList* targetList{getSubList(list, path)}; - if (targetList) { - rawPtr->SetName(name.data()); - targetList->Add(rawPtr); - } else { - LOGF(fatal, "Specified subfolder could not be created."); - } - } - } - - // place lists always at the top - std::function moveListsToTop; - moveListsToTop = [&](TList* list) { - TIter next(list); - TNamed* subList = nullptr; - std::vector subLists; - while ((subList = (TNamed*)next())) { - if (subList->InheritsFrom(TList::Class())) { - subLists.push_back(subList); - moveListsToTop((TList*)subList); - } - } - std::reverse(subLists.begin(), subLists.end()); - for (auto curList : subLists) { - list->Remove(curList); - list->AddFirst(curList); - } - }; - moveListsToTop(list); - - // create dedicated directory containing all of the registrys histograms - if (mCreateRegistryDir) { - // propagate this to the writer by adding a 'flag' to the output list - list->AddLast(new TNamed("createFolder", "")); - } - return list; -} - -// helper function to create resp. find the subList defined by path -TList* HistogramRegistry::getSubList(TList* list, std::deque& path) -{ - if (path.empty()) { - return list; - } - TList* targetList{nullptr}; - std::string nextList = path[0]; - path.pop_front(); - if (auto subList = (TList*)list->FindObject(nextList.data())) { - if (subList->InheritsFrom(TList::Class())) { - targetList = getSubList((TList*)subList, path); - } else { - return nullptr; + if (!rawPtr) { + // Skipping empty histograms + continue; } - } else { - subList = new TList(); - subList->SetName(nextList.data()); - list->Add(subList); - targetList = getSubList(subList, path); + callback(*this, rawPtr); } - return targetList; } // helper function to split user defined path/to/hist/name string diff --git a/Framework/Core/src/IndexBuilderHelpers.cxx b/Framework/Core/src/IndexBuilderHelpers.cxx index 52d6080690fe1..0c06aea11ef2c 100644 --- a/Framework/Core/src/IndexBuilderHelpers.cxx +++ b/Framework/Core/src/IndexBuilderHelpers.cxx @@ -12,140 +12,105 @@ #include "Framework/RuntimeError.h" #include "Framework/IndexBuilderHelpers.h" #include "Framework/CompilerBuiltins.h" -#include +#include "Framework/VariantHelpers.h" +#include +#if (ARROW_VERSION_MAJOR > 20) +#include +#endif #include +#include #include #include #include namespace o2::framework { -void cannotBuildAnArray() +void cannotBuildAnArray(const char* reason) +{ + throw framework::runtime_error_f("Cannot finish an array: %s", reason); +} + +void cannotCreateIndexBuilder() { - throw runtime_error("Cannot build an array"); + throw framework::runtime_error("Cannot create index column builder: invalid kind of index column"); } ChunkedArrayIterator::ChunkedArrayIterator(std::shared_ptr source) - : mSource{source} + : mSource{source}, + mSourceSize{(size_t)source->length()} { mCurrentArray = getCurrentArray(); mCurrent = reinterpret_cast(mCurrentArray->values()->data()) + mOffset; mLast = mCurrent + mCurrentArray->length(); } -SelfIndexColumnBuilder::SelfIndexColumnBuilder(const char* name, arrow::MemoryPool* pool) - : mColumnName{name}, - mArrowType{arrow::int32()} +void ChunkedArrayIterator::reset(std::shared_ptr& source) { - auto status = arrow::MakeBuilder(pool, arrow::int32(), &mBuilder); - if (!status.ok()) { - throw runtime_error("Cannot create array builder!"); - } -} + mPosition = 0; + mChunk = 0; + mOffset = 0; + mCurrentArray = nullptr; + mCurrent = nullptr; + mLast = nullptr; + mFirstIndex = 0; + mSourceSize = 0; -std::shared_ptr SelfIndexColumnBuilder::field() const -{ - return std::make_shared(mColumnName, mArrowType); + mSource = source; + mSourceSize = (size_t)source->length(); + mCurrentArray = getCurrentArray(); + mCurrent = reinterpret_cast(mCurrentArray->values()->data()) + mOffset; + mLast = mCurrent + mCurrentArray->length(); } -IndexColumnBuilder::IndexColumnBuilder(std::shared_ptr source, const char* name, int listSize, arrow::MemoryPool* pool) - : SelfIndexColumnBuilder{name, pool}, - ChunkedArrayIterator{source}, - mListSize{listSize}, - mSourceSize{(size_t)source->length()} +SelfBuilder::SelfBuilder(arrow::MemoryPool* pool) { - switch (mListSize) { - case 1: { - mValueBuilder = mBuilder.get(); - mArrowType = arrow::int32(); - }; break; - case 2: { - if (preSlice().ok()) { - mListBuilder = std::make_unique(pool, std::move(mBuilder), mListSize); - mValueBuilder = static_cast(mListBuilder.get())->value_builder(); - mArrowType = arrow::fixed_size_list(arrow::int32(), 2); - } else { - throw runtime_error("Cannot pre-slice an array"); - } - }; break; - case -1: { - if (preFind().ok()) { - mListBuilder = std::make_unique(pool, std::move(mBuilder)); - mValueBuilder = static_cast(mListBuilder.get())->value_builder(); - mArrowType = arrow::list(arrow::int32()); - } else { - throw runtime_error("Cannot pre-find array groups"); - } - }; break; - default: - throw runtime_error_f("Invalid list size for index column: %d", mListSize); + auto status = arrow::MakeBuilder(pool, arrow::int32(), &mBuilder); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot create array builder for the self-index: %s", status.ToString().c_str()); } } -arrow::Status IndexColumnBuilder::preSlice() +void SelfBuilder::reset(std::shared_ptr) { - arrow::Datum value_counts; - auto options = arrow::compute::ScalarAggregateOptions::Defaults(); - ARROW_ASSIGN_OR_RAISE(value_counts, arrow::compute::CallFunction("value_counts", {mSource}, &options)); - auto pair = static_cast(value_counts.array()); - mValuesArrow = std::make_shared>(pair.field(0)->data()); - mCounts = std::make_shared>(pair.field(1)->data()); - return arrow::Status::OK(); + mBuilder->Reset(); + keyIndex = nullptr; } -arrow::Status IndexColumnBuilder::preFind() +void SelfBuilder::fill(int idx) { - arrow::Datum max; - auto options = arrow::compute::ScalarAggregateOptions::Defaults(); - ARROW_ASSIGN_OR_RAISE(max, arrow::compute::CallFunction("max", {mSource}, &options)); - auto maxValue = std::dynamic_pointer_cast(max.scalar())->value; - mIndices.resize(maxValue + 1); - - auto row = 0; - for (auto i = 0; i < mSource->length(); ++i) { - auto v = valueAt(i); - if (v >= 0) { - mValues.emplace_back(v); - mIndices[v].push_back(row); - } - ++row; + auto status = static_cast(mBuilder.get())->Append(idx); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot append to self-index array: %s", status.ToString().c_str()); } - std::sort(mValues.begin(), mValues.end()); - - return arrow::Status::OK(); } -std::shared_ptr IndexColumnBuilder::resultSingle() const +std::shared_ptr SelfBuilder::result() const { std::shared_ptr array; - auto status = static_cast(mValueBuilder)->Finish(&array); + auto status = static_cast(mBuilder.get())->Finish(&array); if (!status.ok()) { - throw runtime_error("Cannot build an array"); + cannotBuildAnArray(status.ToString().c_str()); } + return std::make_shared(array); } -std::shared_ptr IndexColumnBuilder::resultSlice() const +SingleBuilder::SingleBuilder(std::shared_ptr source, arrow::MemoryPool* pool) + : ChunkedArrayIterator{source} { - std::shared_ptr array; - auto status = static_cast(mListBuilder.get())->Finish(&array); + auto status = arrow::MakeBuilder(pool, arrow::int32(), &mBuilder); if (!status.ok()) { - throw runtime_error("Cannot build an array"); + throw framework::runtime_error_f("Cannot create array builder for the single-valued index: %s", status.ToString().c_str()); } - return std::make_shared(array); } -std::shared_ptr IndexColumnBuilder::resultMulti() const +void SingleBuilder::reset(std::shared_ptr source) { - std::shared_ptr array; - auto status = static_cast(mListBuilder.get())->Finish(&array); - if (!status.ok()) { - throw runtime_error("Cannot build an array"); - } - return std::make_shared(array); + static_cast(this)->reset(source); + mBuilder->Reset(); } -bool IndexColumnBuilder::findSingle(int idx) +bool SingleBuilder::find(int idx) { auto count = mSourceSize - mPosition; while (count > 0) { @@ -166,13 +131,66 @@ bool IndexColumnBuilder::findSingle(int idx) return (mPosition < mSourceSize && valueAt(mPosition) == idx); } -bool IndexColumnBuilder::findSlice(int idx) +void SingleBuilder::fill(int idx) +{ + arrow::Status status; + if (mPosition < mSourceSize && valueAt(mPosition) == idx) { + status = static_cast(mBuilder.get())->Append((int)mPosition); + } else { + status = static_cast(mBuilder.get())->Append(-1); + } + if (!status.ok()) { + throw framework::runtime_error_f("Cannot append to array: %s", status.ToString().c_str()); + } +} + +std::shared_ptr SingleBuilder::result() const +{ + std::shared_ptr array; + auto status = static_cast(mBuilder.get())->Finish(&array); + if (!status.ok()) { + cannotBuildAnArray(status.ToString().c_str()); + } + return std::make_shared(array); +} + +SliceBuilder::SliceBuilder(std::shared_ptr source, arrow::MemoryPool* pool) + : ChunkedArrayIterator{source} { - auto count = mValuesArrow->length() - mValuePos; + auto status = preSlice(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot pre-slice the source for slice-index building: %s", status.ToString().c_str()); + } + + std::unique_ptr builder; + status = arrow::MakeBuilder(pool, arrow::int32(), &builder); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot create array for the slice-index builder: %s", status.ToString().c_str()); + } + mListBuilder = std::make_unique(pool, std::move(builder), 2); + mValueBuilder = static_cast(mListBuilder.get())->value_builder(); +} + +void SliceBuilder::reset(std::shared_ptr source) +{ + mValues = nullptr; + mCounts = nullptr; + mListBuilder->Reset(); + mValuePos = 0; + static_cast(this)->reset(source); + auto status = preSlice(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot pre-slice the source for slice-index building: %s", status.ToString().c_str()); + } +} + +bool SliceBuilder::find(int idx) +{ + auto count = mValues->length() - mValuePos; while (count > 0) { auto step = count / 2; mValuePos += step; - if (mValuesArrow->Value(mValuePos) <= idx) { + if (mValues->Value(mValuePos) <= idx) { count -= step + 1; } else { mValuePos -= step; @@ -180,32 +198,17 @@ bool IndexColumnBuilder::findSlice(int idx) } } - if (mValuePos < mValuesArrow->length() && mValuesArrow->Value(mValuePos) <= idx) { + if (mValuePos < mValues->length() && mValues->Value(mValuePos) <= idx) { ++mPosition; } - return (mValuePos < mValuesArrow->length() && mValuesArrow->Value(mValuePos) == idx); -} - -bool IndexColumnBuilder::findMulti(int idx) -{ - return (std::find(mValues.begin(), mValues.end(), idx) != mValues.end()); + return (mValuePos < mValues->length() && mValues->Value(mValuePos) == idx); } -void IndexColumnBuilder::fillSingle(int idx) -{ - // entry point - if (mPosition < mSourceSize && valueAt(mPosition) == idx) { - (void)static_cast(mValueBuilder)->Append((int)mPosition); - } else { - (void)static_cast(mValueBuilder)->Append(-1); - } -} - -void IndexColumnBuilder::fillSlice(int idx) +void SliceBuilder::fill(int idx) { int data[2] = {-1, -1}; - if (mValuePos < mValuesArrow->length() && mValuesArrow->Value(mValuePos) == idx) { + if (mValuePos < mValues->length() && mValues->Value(mValuePos) == idx) { for (auto i = 0; i < mValuePos; ++i) { data[0] += mCounts->Value(i); } @@ -216,7 +219,70 @@ void IndexColumnBuilder::fillSlice(int idx) (void)static_cast(mValueBuilder)->AppendValues(data, 2); } -void IndexColumnBuilder::fillMulti(int idx) +std::shared_ptr SliceBuilder::result() const +{ + std::shared_ptr array; + auto status = static_cast(mListBuilder.get())->Finish(&array); + if (!status.ok()) { + cannotBuildAnArray(status.ToString().c_str()); + } + return std::make_shared(array); +} + +arrow::Status SliceBuilder::SliceBuilder::preSlice() +{ +#if (ARROW_VERSION_MAJOR > 20) + auto status = arrow::compute::Initialize(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot initialize arrow compute: %s", status.ToString().c_str()); + } +#else + arrow::Status status; +#endif + arrow::Datum value_counts; + auto options = arrow::compute::ScalarAggregateOptions::Defaults(); + ARROW_ASSIGN_OR_RAISE(value_counts, arrow::compute::CallFunction("value_counts", {mSource}, &options)); + auto pair = static_cast(value_counts.array()); + mValues = std::make_shared>(pair.field(0)->data()); + mCounts = std::make_shared>(pair.field(1)->data()); + return arrow::Status::OK(); +} + +ArrayBuilder::ArrayBuilder(std::shared_ptr source, arrow::MemoryPool* pool) + : ChunkedArrayIterator{source} +{ + auto&& status = preFind(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot pre-find in a source for array-index building: %s", status.ToString().c_str()); + } + + std::unique_ptr builder; + status = arrow::MakeBuilder(pool, arrow::int32(), &builder); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot create array for the array-index builder: %s", status.ToString().c_str()); + } + mListBuilder = std::make_unique(pool, std::move(builder)); + mValueBuilder = static_cast(mListBuilder.get())->value_builder(); +} + +void ArrayBuilder::reset(std::shared_ptr source) +{ + static_cast(this)->reset(source); + auto status = preFind(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot pre-find in a source for array-index building: %s", status.ToString().c_str()); + } + mValues.clear(); + mIndices.clear(); + mListBuilder->Reset(); +} + +bool ArrayBuilder::find(int idx) +{ + return (std::find(mValues.begin(), mValues.end(), idx) != mValues.end()); +} + +void ArrayBuilder::fill(int idx) { (void)static_cast(mListBuilder.get())->Append(); if (std::find(mValues.begin(), mValues.end(), idx) != mValues.end()) { @@ -226,6 +292,104 @@ void IndexColumnBuilder::fillMulti(int idx) } } +std::shared_ptr ArrayBuilder::result() const +{ + std::shared_ptr array; + auto status = static_cast(mListBuilder.get())->Finish(&array); + if (!status.ok()) { + cannotBuildAnArray(status.ToString().c_str()); + } + return std::make_shared(array); +} + +arrow::Status ArrayBuilder::preFind() +{ +#if (ARROW_VERSION_MAJOR > 20) + auto status = arrow::compute::Initialize(); + if (!status.ok()) { + throw framework::runtime_error_f("Cannot initialize arrow compute: %s", status.ToString().c_str()); + } +#else + arrow::Status status; +#endif + arrow::Datum max; + auto options = arrow::compute::ScalarAggregateOptions::Defaults(); + ARROW_ASSIGN_OR_RAISE(max, arrow::compute::CallFunction("max", {mSource}, &options)); + auto maxValue = std::dynamic_pointer_cast(max.scalar())->value; + mIndices.resize(maxValue + 1); + + auto row = 0; + for (auto i = 0; i < mSource->length(); ++i) { + auto v = valueAt(i); + if (v >= 0) { + mValues.emplace_back(v); + mIndices[v].push_back(row); + } + ++row; + } + std::sort(mValues.begin(), mValues.end()); + + return arrow::Status::OK(); +} + +IndexColumnBuilder::IndexColumnBuilder(soa::IndexKind kind, int pos, arrow::MemoryPool* pool, std::shared_ptr source) + : mColumnPos{pos} +{ + switch (kind) { + case soa::IndexKind::IdxSelf: + builder = SelfBuilder{pool}; + break; + case soa::IndexKind::IdxSingle: + builder = SingleBuilder{source, pool}; + break; + case soa::IndexKind::IdxSlice: + builder = SliceBuilder{source, pool}; + break; + case soa::IndexKind::IdxArray: + builder = ArrayBuilder{source, pool}; + break; + default: + cannotCreateIndexBuilder(); + } +} + +void IndexColumnBuilder::reset(std::shared_ptr source) +{ + std::visit( + overloaded{ + [](std::monostate) {}, + [&source](auto& b) { b.reset(source); }}, + builder); +} + +bool IndexColumnBuilder::find(int idx) +{ + return std::visit( + overloaded{ + [](std::monostate) { return false; }, + [&idx](auto& b) { return b.find(idx); }, + }, + builder); +} + +void IndexColumnBuilder::fill(int idx) +{ + std::visit( + overloaded{ + [](std::monostate) {}, + [&idx](auto& b) { b.fill(idx); }}, + builder); +} + +std::shared_ptr IndexColumnBuilder::result() const +{ + return std::visit( + overloaded{ + [](std::monostate) -> std::shared_ptr { return nullptr; }, + [](auto& b) { return b.result(); }}, + builder); +} + std::shared_ptr ChunkedArrayIterator::getCurrentArray() { auto chunk = mSource->chunk(mChunk); @@ -265,14 +429,4 @@ int ChunkedArrayIterator::valueAt(size_t pos) } return *(mCurrent + pos); } - -std::shared_ptr makeArrowTable(const char* label, std::vector>&& columns, std::vector>&& fields) -{ - auto schema = std::make_shared(fields); - schema->WithMetadata( - std::make_shared( - std::vector{std::string{"label"}}, - std::vector{std::string{label}})); - return arrow::Table::Make(schema, columns); -} } // namespace o2::framework diff --git a/Framework/Core/src/IndexJSONHelpers.cxx b/Framework/Core/src/IndexJSONHelpers.cxx new file mode 100644 index 0000000000000..a5c6c70579599 --- /dev/null +++ b/Framework/Core/src/IndexJSONHelpers.cxx @@ -0,0 +1,240 @@ +// Copyright 2019-2025 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 "IndexJSONHelpers.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace o2::framework +{ +namespace +{ +struct IndexRecordsReader : public rapidjson::BaseReaderHandler, IndexRecordsReader> { + using Ch = rapidjson::UTF8<>::Ch; + using SizeType = rapidjson::SizeType; + + enum struct State { + IN_START, + IN_LIST, + IN_RECORD, + IN_ERROR + }; + + std::stack states; + std::ostringstream debug; + + std::vector records; + std::string currentKey; + std::string label; + std::string columnLabel; + std::string matcherStr; + o2::soa::IndexKind kind; + int pos; + + IndexRecordsReader() + { + debug << ">>> Start" << std::endl; + states.push(State::IN_START); + } + + bool StartArray() + { + debug << "StartArray()" << std::endl; + if (states.top() == State::IN_START && currentKey.compare("records") == 0) { + states.push(State::IN_LIST); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool EndArray(SizeType) + { + debug << "EndArray()" << std::endl; + if (states.top() == State::IN_LIST) { + // records done + states.pop(); + return true; + } + states.push(State::IN_ERROR); + return false; + } + + bool Key(const Ch* str, SizeType, bool) + { + debug << "Key(" << str << ")" << std::endl; + currentKey = str; + if (states.top() == State::IN_START) { + if (currentKey.compare("records") == 0) { + return true; + } + } + + if (states.top() == State::IN_RECORD) { + if (currentKey.compare("label") == 0) { + return true; + } + if (currentKey.compare("matcher") == 0) { + return true; + } + if (currentKey.compare("column") == 0) { + return true; + } + if (currentKey.compare("kind") == 0) { + return true; + } + if (currentKey.compare("pos") == 0) { + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } + + bool StartObject() + { + debug << "StartObject()" << std::endl; + if (states.top() == State::IN_START) { + return true; + } + + if (states.top() == State::IN_LIST) { + states.push(State::IN_RECORD); + label = ""; + kind = soa::IndexKind::IdxInvalid; + pos = -2; + return true; + } + + states.push(State::IN_ERROR); + return false; + } + + bool EndObject(SizeType) + { + debug << "EndObject()" << std::endl; + if (states.top() == State::IN_RECORD) { + states.pop(); + // add a record + records.emplace_back(label, DataSpecUtils::fromString(matcherStr), columnLabel, kind, pos); + return true; + } + + if (states.top() == State::IN_START) { + return true; + } + + states.push(State::IN_ERROR); + return false; + } + + bool Uint(unsigned i) + { + debug << "Uint(" << i << ") passed to Int()" << std::endl; + return Int(i); + } + + bool Int(int i) + { + debug << "Int(" << i << ")" << std::endl; + if (states.top() == State::IN_RECORD) { + if (currentKey.compare("kind") == 0) { + kind = (soa::IndexKind)i; + return true; + } + if (currentKey.compare("pos") == 0) { + pos = i; + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } + + bool String(const Ch* str, SizeType, bool) + { + debug << "String(" << str << ")" << std::endl; + if (states.top() == State::IN_RECORD) { + if (currentKey.compare("label") == 0) { + label = str; + return true; + } + if (currentKey.compare("column") == 0) { + columnLabel = str; + return true; + } + if (currentKey.compare("matcher") == 0) { + matcherStr = str; + return true; + } + } + + states.push(State::IN_ERROR); + return false; + } +}; +} // namespace + +std::vector IndexJSONHelpers::read(std::istream& s) +{ + rapidjson::Reader reader; + rapidjson::IStreamWrapper isw(s); + IndexRecordsReader irreader; + + bool ok = reader.Parse(isw, irreader); + + if (!ok) { + throw framework::runtime_error_f("Cannot parse serialized index records vector, error: %s at offset: %d", rapidjson::GetParseError_En(reader.GetParseErrorCode()), reader.GetErrorOffset()); + } + return irreader.records; +} + +namespace +{ +void writeRecords(rapidjson::Writer& w, std::vector& records) +{ + for (auto& r : records) { + w.StartObject(); + w.Key("label"); + w.String(r.label.c_str()); + w.Key("matcher"); + w.String(DataSpecUtils::describe(r.matcher).c_str()); + w.Key("column"); + w.String(r.columnLabel.c_str()); + w.Key("kind"); + w.Int((int)r.kind); + w.Key("pos"); + w.Int(r.pos); + w.EndObject(); + } +} +} // namespace + +void IndexJSONHelpers::write(std::ostream& o, std::vector& irs) +{ + rapidjson::OStreamWrapper osw(o); + rapidjson::Writer w(osw); + w.StartObject(); + w.Key("records"); + w.StartArray(); + writeRecords(w, irs); + w.EndArray(); + w.EndObject(); +} +} // namespace o2::framework diff --git a/Framework/Core/src/IndexJSONHelpers.h b/Framework/Core/src/IndexJSONHelpers.h new file mode 100644 index 0000000000000..dee534ae390f5 --- /dev/null +++ b/Framework/Core/src/IndexJSONHelpers.h @@ -0,0 +1,25 @@ +// Copyright 2019-2025 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 INDEXJSONHELPERS_H +#define INDEXJSONHELPERS_H + +#include + +namespace o2::framework +{ +struct IndexJSONHelpers { + static std::vector read(std::istream& s); + static void write(std::ostream& o, std::vector& irs); +}; + +} // namespace o2::framework + +#endif // INDEXJSONHELPERS_H diff --git a/Framework/Core/src/InputRecord.cxx b/Framework/Core/src/InputRecord.cxx index c4c6852a6a3c7..7bc9907b13ba4 100644 --- a/Framework/Core/src/InputRecord.cxx +++ b/Framework/Core/src/InputRecord.cxx @@ -58,6 +58,11 @@ int InputRecord::getPos(const char* binding) const return -1; } +int InputRecord::getPos(ConcreteDataMatcher matcher) const +{ + return getPos(mInputsSchema, matcher).index; +} + InputRecord::InputPos InputRecord::getPos(std::vector const& schema, ConcreteDataMatcher concrete) { size_t inputIndex = 0; @@ -134,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/O2ControlHelpers.cxx b/Framework/Core/src/O2ControlHelpers.cxx index 273950e5047f0..c8027c20234b8 100644 --- a/Framework/Core/src/O2ControlHelpers.cxx +++ b/Framework/Core/src/O2ControlHelpers.cxx @@ -369,7 +369,7 @@ void dumpTask(std::ostream& dumpOut, const DeviceSpec& spec, const DeviceExecuti dumpOut << indLevel << "defaults:\n"; dumpOut << indLevel << indScheme << "log_task_stdout: none\n"; dumpOut << indLevel << indScheme << "log_task_stderr: none\n"; - std::string exitTransitionTimeout = "25"; // Allow 25 seconds to finish processing and calibrations + std::string exitTransitionTimeout = "40"; // Allow 40 seconds to finish processing and calibrations std::string dataProcessingTimeout = "20"; // Allow only 20 seconds to finish processing if (execution.args.size() > 2) { for (size_t i = 0; i < execution.args.size() - 1; ++i) { diff --git a/Framework/Core/src/OptionsHelpers.h b/Framework/Core/src/OptionsHelpers.h index 578ccc4935e69..3ca2dc1df330e 100644 --- a/Framework/Core/src/OptionsHelpers.h +++ b/Framework/Core/src/OptionsHelpers.h @@ -13,7 +13,7 @@ #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include -#include +#include namespace boost::program_options { 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/PropertyTreeHelpers.cxx b/Framework/Core/src/PropertyTreeHelpers.cxx index 3f2356eb37824..18055edb635b4 100644 --- a/Framework/Core/src/PropertyTreeHelpers.cxx +++ b/Framework/Core/src/PropertyTreeHelpers.cxx @@ -14,12 +14,15 @@ #include "Framework/VariantPropertyTreeHelpers.h" #include "Framework/RuntimeError.h" #include "Framework/VariantJSONHelpers.h" +#include "Framework/Signpost.h" #include #include #include +O2_DECLARE_DYNAMIC_LOG(configuration); + namespace o2::framework { namespace @@ -37,6 +40,8 @@ void PropertyTreeHelpers::populateDefaults(std::vector const& s boost::property_tree::ptree& pt, boost::property_tree::ptree& provenance) { + O2_SIGNPOST_ID_GENERATE(cid, configuration); + O2_SIGNPOST_START(configuration, cid, "populateDefaults", "Filling with defaults"); for (auto const& spec : schema) { std::string key = spec.name.substr(0, spec.name.find(',')); try { @@ -77,9 +82,12 @@ void PropertyTreeHelpers::populateDefaults(std::vector const& s case VariantType::String: pt.put(key, spec.defaultValue.get()); break; - case VariantType::Bool: - pt.put(key, spec.defaultValue.get()); + case VariantType::Bool: { + bool value = spec.defaultValue.get(); + O2_SIGNPOST_EVENT_EMIT(configuration, cid, "populateDefaults", "Setting %{public}s: %{public}s", key.c_str(), value ? "true" : "false"); + pt.put(key, value); break; + } case VariantType::Dict: pt.put_child(key, boost::property_tree::ptree{}); break; @@ -126,13 +134,17 @@ void PropertyTreeHelpers::populateDefaults(std::vector const& s } provenance.put(key, "default"); } catch (std::runtime_error& re) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populateDefaults", "Aborting because of runtime_error %{public}s", re.what()); throw; } catch (std::exception& e) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populateDefaults", "Missing option %{public}s (%{public}s)", key.c_str(), e.what()); throw std::invalid_argument(std::string("missing option: ") + key + " (" + e.what() + ")"); } catch (...) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populateDefaults", "Aborting because of missing option %{public}s", key.c_str()); throw std::invalid_argument(std::string("missing option: ") + key); } } + O2_SIGNPOST_END(configuration, cid, "populateDefaults", "Done"); } void PropertyTreeHelpers::populate(std::vector const& schema, @@ -140,6 +152,8 @@ void PropertyTreeHelpers::populate(std::vector const& schema, boost::program_options::variables_map const& vmap, boost::property_tree::ptree& provenance) { + O2_SIGNPOST_ID_GENERATE(cid, configuration); + O2_SIGNPOST_START(configuration, cid, "populate", "Filling parameters from variables_map"); for (auto const& spec : schema) { // strip short version to get the correct key std::string key = spec.name.substr(0, spec.name.find(',')); @@ -183,9 +197,11 @@ void PropertyTreeHelpers::populate(std::vector const& schema, pt.put(key, *v); } break; - case VariantType::Bool: - pt.put(key, vmap[key].as()); - break; + case VariantType::Bool: { + auto v = vmap[key].as(); + O2_SIGNPOST_EVENT_EMIT(configuration, cid, "populate", "Setting %{public}s: %{public}s", key.c_str(), v ? "true" : "false"); + pt.put(key, v); + } break; case VariantType::ArrayInt: { auto v = fromString(vmap[key].as()); pt.put_child(key, vectorToBranch(v.get(), v.size())); @@ -243,13 +259,17 @@ void PropertyTreeHelpers::populate(std::vector const& schema, } provenance.put(key, "fairmq"); } catch (std::runtime_error& re) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Aborting because of runtime_error %{public}s", re.what()); throw; } catch (std::exception& e) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Missing option %{public}s (%{public}s)", key.c_str(), e.what()); throw std::invalid_argument(std::string("missing option: ") + key + " (" + e.what() + ")"); } catch (...) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Aborting because of missing option %{public}s", key.c_str()); throw std::invalid_argument(std::string("missing option: ") + key); } } + O2_SIGNPOST_END(configuration, cid, "populate", "Done"); } template @@ -273,6 +293,8 @@ void PropertyTreeHelpers::populate(std::vector const& schema, boost::property_tree::ptree& provenance, std::string const& provenanceLabel) { + O2_SIGNPOST_ID_GENERATE(cid, configuration); + O2_SIGNPOST_START(configuration, cid, "populate", "Filling parameters from ptree"); for (auto const& spec : schema) { // strip short version to get the correct key std::string key = spec.name.substr(0, spec.name.find(',')); @@ -318,9 +340,11 @@ void PropertyTreeHelpers::populate(std::vector const& schema, case VariantType::String: pt.put(key, (*it).get_value()); break; - case VariantType::Bool: + case VariantType::Bool: { + auto v = (*it).get_value(); + O2_SIGNPOST_EVENT_EMIT(configuration, cid, "populate", "Setting %{public}s: %{public}s", key.c_str(), v ? "true" : "false"); pt.put(key, (*it).get_value()); - break; + } break; case VariantType::Dict: case VariantType::ArrayInt: case VariantType::ArrayFloat: @@ -371,13 +395,18 @@ void PropertyTreeHelpers::populate(std::vector const& schema, } provenance.put(key, provenanceLabel); } catch (std::runtime_error& re) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Aborting during processing of %{public}s because of runtime_error %{public}s", + key.c_str(), re.what()); throw; } catch (std::exception& e) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Missing option %{public}s (%{public}s)", key.c_str(), e.what()); throw std::invalid_argument(std::string("missing option: ") + key + " (" + e.what() + ")"); } catch (...) { + O2_SIGNPOST_END_WITH_ERROR(configuration, cid, "populate", "Aborting because of missing option %{public}s", key.c_str()); throw std::invalid_argument(std::string("missing option: ") + key); } } + O2_SIGNPOST_END(configuration, cid, "populate", "Done"); } namespace diff --git a/Framework/Core/src/ResourcePolicy.cxx b/Framework/Core/src/ResourcePolicy.cxx index 9076a87d547fa..18ff15e372657 100644 --- a/Framework/Core/src/ResourcePolicy.cxx +++ b/Framework/Core/src/ResourcePolicy.cxx @@ -18,8 +18,9 @@ namespace o2::framework std::vector ResourcePolicy::createDefaultPolicies() { + // FIXME: we should have better logic to decide if we can process something. return { - ResourcePolicyHelpers::sharedMemoryBoundTask("internal-dpl-aod-reader.*", 100000000), + ResourcePolicyHelpers::rateLimitedSharedMemoryBoundTask("internal-dpl-aod-reader.*", 100000000, 1), ResourcePolicyHelpers::trivialTask(".*")}; } diff --git a/Framework/Core/src/ResourcePolicyHelpers.cxx b/Framework/Core/src/ResourcePolicyHelpers.cxx index aad783cdc1f60..650beec3ac599 100644 --- a/Framework/Core/src/ResourcePolicyHelpers.cxx +++ b/Framework/Core/src/ResourcePolicyHelpers.cxx @@ -11,9 +11,7 @@ #include "Framework/ResourcePolicyHelpers.h" #include "Framework/DeviceSpec.h" -#include "ResourcesMonitoringHelper.h" -#include #include namespace o2::framework @@ -38,7 +36,39 @@ 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) +{ + return ResourcePolicy{ + "ratelimited-shm-bound", + [matcher = std::regex(s)](DeviceSpec const& spec) -> bool { + return std::regex_match(spec.name, matcher); + }, + [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) { + return OfferScore::Unneeded; + } + // If we have enough timeslices and not enough shared memory + // ignore further timeslices. + if (accumulated.timeslices >= requestedTimeslices && offer.sharedMemory == 0) { + return OfferScore::Unneeded; + } + // If it does not offer neither shared memory nor timeslices, mark it as unneeded. + if (offer.sharedMemory == 0 && offer.timeslices == 0) { + return OfferScore::Unneeded; + } + // We have enough to process. + if ((accumulated.sharedMemory + offer.sharedMemory) >= requestedSharedMemory && (accumulated.timeslices + offer.timeslices) >= requestedTimeslices) { + return OfferScore::Enough; + } + // We need more resources + return OfferScore::More; }, + ComputingQuotaOffer{.sharedMemory = requestedSharedMemory, .timeslices = requestedTimeslices}}; } ResourcePolicy ResourcePolicyHelpers::sharedMemoryBoundTask(char const* s, int requestedSharedMemory) @@ -48,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 >= 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/ResourcesMonitoringHelper.cxx b/Framework/Core/src/ResourcesMonitoringHelper.cxx index 1f69da80888c9..d457edb4844e5 100644 --- a/Framework/Core/src/ResourcesMonitoringHelper.cxx +++ b/Framework/Core/src/ResourcesMonitoringHelper.cxx @@ -8,12 +8,13 @@ // 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 "ResourcesMonitoringHelper.h" #include "Framework/DeviceMetricsInfo.h" -#define BOOST_BIND_GLOBAL_PLACEHOLDERS -#include -#include +#include +#include + +#include +#include #include #include #include @@ -22,153 +23,147 @@ using namespace o2::framework; template -inline static T retriveValue(T val) -{ - return val; -} - -inline static std::string retriveValue(const std::reference_wrapper val) +void fillNodeWithValue(rapidjson::Writer& w, + size_t filledMetrics, + MetricsStorage const& metricsStorage, + TimestampsStorage const& timestampsStorage) { - return std::string(val.get().data); -} - -template -boost::property_tree::ptree fillNodeWithValue(const DeviceMetricsInfo& deviceMetrics, - const T& metricsStorage, const TIMESTAMPS& timestampsStorage, size_t labelIndex, size_t storeIndex) -{ - unsigned int loopRange = std::min(deviceMetrics.metrics[labelIndex].filledMetrics, metricsStorage[storeIndex].size()); - boost::property_tree::ptree metricNode; + unsigned int loopRange = std::min(filledMetrics, metricsStorage.size()); + w.StartArray(); for (unsigned int idx = 0; idx < loopRange; ++idx) { - boost::property_tree::ptree values; - values.add("timestamp", timestampsStorage[storeIndex][idx]); + w.StartObject(); + w.Key("timestamp"); + std::string s = std::to_string(timestampsStorage[idx]); + w.String(s.c_str(), s.size()); + w.Key("value"); if constexpr (std::is_arithmetic_v) { - values.add("value", std::to_string(retriveValue(std::cref(metricsStorage[storeIndex][idx])))); + w.String(std::to_string(metricsStorage[idx]).c_str()); } else { - values.add("value", retriveValue(std::cref(metricsStorage[storeIndex][idx]))); + w.String(metricsStorage[idx].data); } - metricNode.push_back(std::make_pair("", values)); + w.EndObject(); } - return metricNode; + w.EndArray(); } bool ResourcesMonitoringHelper::dumpMetricsToJSON(const std::vector& metrics, const DeviceMetricsInfo& driverMetrics, const std::vector& specs, - std::vector const& performanceMetricsRegex) noexcept + std::vector const& performanceMetricsRegex, + std::ostream& out) noexcept { - assert(metrics.size() == specs.size()); if (metrics.empty()) { return false; } - boost::property_tree::ptree root; - for (unsigned int idx = 0; idx < metrics.size(); ++idx) { + rapidjson::OStreamWrapper osw(out); + rapidjson::PrettyWriter w(osw); + // Top level obejct for all the metrics + w.StartObject(); + + for (unsigned int idx = 0; idx < metrics.size(); ++idx) { + w.Key(specs[idx].id.c_str()); const auto& deviceMetrics = metrics[idx]; - boost::property_tree::ptree deviceRoot; + w.StartObject(); for (size_t mi = 0; mi < deviceMetrics.metricLabels.size(); mi++) { std::string_view metricLabel{deviceMetrics.metricLabels[mi].label, deviceMetrics.metricLabels[mi].size}; auto same = [metricLabel](std::regex const& matcher) -> bool { return std::regex_match(metricLabel.begin(), metricLabel.end(), matcher); }; - //check if we are interested + // check if we are interested if (std::find_if(std::begin(performanceMetricsRegex), std::end(performanceMetricsRegex), same) == performanceMetricsRegex.end()) { continue; } auto storeIdx = deviceMetrics.metrics[mi].storeIdx; + size_t filledMetrics = deviceMetrics.metrics[mi].filledMetrics; if (deviceMetrics.metrics[mi].filledMetrics == 0) { continue; } - //if so - - boost::property_tree::ptree metricNode; - + w.Key(metricLabel.data(), metricLabel.size()); switch (deviceMetrics.metrics[mi].type) { case MetricType::Int: - metricNode = fillNodeWithValue(deviceMetrics, deviceMetrics.intMetrics, deviceMetrics.intTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, deviceMetrics.intMetrics[storeIdx], + deviceMetrics.intTimestamps[storeIdx]); break; case MetricType::Float: - metricNode = fillNodeWithValue(deviceMetrics, deviceMetrics.floatMetrics, deviceMetrics.floatTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, deviceMetrics.floatMetrics[storeIdx], + deviceMetrics.floatTimestamps[storeIdx]); break; case MetricType::String: - metricNode = fillNodeWithValue(deviceMetrics, deviceMetrics.stringMetrics, deviceMetrics.stringTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, deviceMetrics.stringMetrics[storeIdx], + deviceMetrics.stringTimestamps[storeIdx]); break; case MetricType::Uint64: - metricNode = fillNodeWithValue(deviceMetrics, deviceMetrics.uint64Metrics, deviceMetrics.uint64Timestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, deviceMetrics.uint64Metrics[storeIdx], + deviceMetrics.uint64Timestamps[storeIdx]); break; default: continue; } - deviceRoot.add_child(std::string(metricLabel), metricNode); } - root.add_child(specs[idx].id, deviceRoot); + w.EndObject(); } - boost::property_tree::ptree driverRoot; + w.Key("driver"); + w.StartObject(); for (size_t mi = 0; mi < driverMetrics.metricLabels.size(); mi++) { std::string_view const metricLabel{driverMetrics.metricLabels[mi].label, driverMetrics.metricLabels[mi].size}; auto same = [metricLabel](std::regex const& matcher) -> bool { return std::regex_match(metricLabel.begin(), metricLabel.end(), matcher); }; - //check if we are interested + // check if we are interested if (std::find_if(std::begin(performanceMetricsRegex), std::end(performanceMetricsRegex), same) == performanceMetricsRegex.end()) { continue; } auto storeIdx = driverMetrics.metrics[mi].storeIdx; // and if data is there - if (driverMetrics.metrics[mi].filledMetrics == 0) { + size_t filledMetrics = driverMetrics.metrics[mi].filledMetrics; + if (filledMetrics == 0) { continue; } - //if so - boost::property_tree::ptree metricNode; - + w.Key(metricLabel.data(), metricLabel.size()); switch (driverMetrics.metrics[mi].type) { case MetricType::Int: - metricNode = fillNodeWithValue(driverMetrics, driverMetrics.intMetrics, driverMetrics.intTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, driverMetrics.intMetrics[storeIdx], + driverMetrics.intTimestamps[storeIdx]); break; case MetricType::Float: - metricNode = fillNodeWithValue(driverMetrics, driverMetrics.floatMetrics, driverMetrics.floatTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, driverMetrics.floatMetrics[storeIdx], + driverMetrics.floatTimestamps[storeIdx]); break; case MetricType::String: - metricNode = fillNodeWithValue(driverMetrics, driverMetrics.stringMetrics, driverMetrics.stringTimestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, driverMetrics.stringMetrics[storeIdx], + driverMetrics.stringTimestamps[storeIdx]); break; case MetricType::Uint64: - metricNode = fillNodeWithValue(driverMetrics, driverMetrics.uint64Metrics, driverMetrics.uint64Timestamps, mi, storeIdx); + fillNodeWithValue(w, filledMetrics, driverMetrics.uint64Metrics[storeIdx], + driverMetrics.uint64Timestamps[storeIdx]); break; default: continue; } - driverRoot.add_child(std::string{metricLabel}, metricNode); } - - root.add_child("driver", driverRoot); - - std::ofstream file("performanceMetrics.json", std::ios::out); - if (file.is_open()) { - boost::property_tree::json_parser::write_json(file, root); - } else { - return false; - } - - file.close(); + w.EndObject(); + w.EndObject(); return true; } diff --git a/Framework/Core/src/ResourcesMonitoringHelper.h b/Framework/Core/src/ResourcesMonitoringHelper.h index fc725e201ea98..549f681231e35 100644 --- a/Framework/Core/src/ResourcesMonitoringHelper.h +++ b/Framework/Core/src/ResourcesMonitoringHelper.h @@ -13,27 +13,25 @@ #define O2_FRAMEWORK_RESOURCESMONITORINGHELPER_H_ #include "Framework/DeviceMetricsInfo.h" -#include "Monitoring/ProcessMonitor.h" #include "Framework/DeviceSpec.h" #include -#include #include +#include namespace o2::framework { - struct ResourcesMonitoringHelper { /// Dump the metrics in @a metrics which match the names specified in @a metricsToDump /// @a specs are the DeviceSpecs associated to the metrics. static bool dumpMetricsToJSON(std::vector const& metrics, DeviceMetricsInfo const& driverMetrics, std::vector const& specs, - std::vector const& metricsToDump) noexcept; + std::vector const& metricsToDump, + std::ostream& out) noexcept; static bool isResourcesMonitoringEnabled(unsigned short interval) noexcept { return interval > 0; } }; - } // namespace o2::framework #endif // O2_FRAMEWORK_RESOURCESMONITORINGHELPER_H_ diff --git a/Framework/Core/src/RootArrowFilesystem.cxx b/Framework/Core/src/RootArrowFilesystem.cxx index 403e393ec6090..6976c710062e6 100644 --- a/Framework/Core/src/RootArrowFilesystem.cxx +++ b/Framework/Core/src/RootArrowFilesystem.cxx @@ -34,18 +34,21 @@ namespace o2::framework { using arrow::Status; -TFileFileSystem::TFileFileSystem(TDirectoryFile* f, size_t readahead, RootObjectReadingFactory& factory) +TFileFileSystem::TFileFileSystem(TDirectoryFile* f, size_t readahead, RootObjectReadingFactory& factory, bool ownsFile) : VirtualRootFileSystemBase(), mFile(f), - mObjectFactory(factory) + mObjectFactory(factory), + mOwnsFile(ownsFile) { ((TFile*)mFile)->SetReadaheadSize(50 * 1024 * 1024); } TFileFileSystem::~TFileFileSystem() { - mFile->Close(); - delete mFile; + if (mOwnsFile) { + mFile->Close(); + delete mFile; + } } std::shared_ptr TFileFileSystem::GetObjectHandler(arrow::dataset::FileSource source) diff --git a/Framework/Core/src/ServiceRegistryRef.cxx b/Framework/Core/src/ServiceRegistryRef.cxx new file mode 100644 index 0000000000000..70728ad37eda7 --- /dev/null +++ b/Framework/Core/src/ServiceRegistryRef.cxx @@ -0,0 +1,25 @@ +// 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/ServiceRegistryRef.h" +namespace o2::framework { + +ServiceRegistryRef *ServiceRegistryRef::globalDeviceRef(ServiceRegistryRef *ref) { + static ServiceRegistryRef *globalRef = nullptr; + if (!globalRef) { + globalRef = ref; + } + // We return a copy, so that it can be cache + return globalRef; +} + +} 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/TMessageSerializer.cxx b/Framework/Core/src/TMessageSerializer.cxx index 81a1c6e537d09..bf9583f780957 100644 --- a/Framework/Core/src/TMessageSerializer.cxx +++ b/Framework/Core/src/TMessageSerializer.cxx @@ -9,7 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. #include -#include +#include #include #include diff --git a/Framework/Core/src/TableBuilder.cxx b/Framework/Core/src/TableBuilder.cxx index 2169722efa9da..955fe686e12a8 100644 --- a/Framework/Core/src/TableBuilder.cxx +++ b/Framework/Core/src/TableBuilder.cxx @@ -81,53 +81,7 @@ void TableBuilder::validate() const void TableBuilder::setLabel(const char* label) { - mSchema = mSchema->WithMetadata(std::make_shared(std::vector{std::string{"label"}}, std::vector{std::string{label}})); -} - -std::shared_ptr spawnerHelper(std::shared_ptr const& fullTable, std::shared_ptr newSchema, size_t nColumns, - expressions::Projector* projectors, const char* name, - std::shared_ptr& projector) -{ - if (projector == nullptr) { - projector = framework::expressions::createProjectorHelper(nColumns, projectors, fullTable->schema(), newSchema->fields()); - } - - arrow::TableBatchReader reader(*fullTable); - std::shared_ptr batch; - arrow::ArrayVector v; - std::vector chunks; - chunks.resize(nColumns); - std::vector> arrays; - - while (true) { - auto s = reader.ReadNext(&batch); - if (!s.ok()) { - throw runtime_error_f("Cannot read batches from source table to spawn %s: %s", name, s.ToString().c_str()); - } - if (batch == nullptr) { - break; - } - try { - s = projector->Evaluate(*batch, arrow::default_memory_pool(), &v); - if (!s.ok()) { - throw runtime_error_f("Cannot apply projector to source table of %s: %s", name, s.ToString().c_str()); - } - } catch (std::exception& e) { - throw runtime_error_f("Cannot apply projector to source table of %s: exception caught: %s", name, e.what()); - } - - for (auto i = 0U; i < nColumns; ++i) { - chunks[i].emplace_back(v.at(i)); - } - } - - arrays.reserve(nColumns); - for (auto i = 0U; i < nColumns; ++i) { - arrays.push_back(std::make_shared(chunks[i])); - } - - addLabelToSchema(newSchema, name); - return arrow::Table::Make(newSchema, arrays); + addLabelToSchema(mSchema, label); } } // namespace o2::framework diff --git a/Framework/Core/src/Variant.cxx b/Framework/Core/src/Variant.cxx index 21eb6103aa56a..e54a973bd4413 100644 --- a/Framework/Core/src/Variant.cxx +++ b/Framework/Core/src/Variant.cxx @@ -89,29 +89,29 @@ Variant::Variant(const Variant& other) : mType(other.mType) // In case this is an array we need to duplicate it to avoid // double deletion. switch (mType) { - case variant_trait_v: + case VariantType::String: mSize = other.mSize; variant_helper::set(&mStore, other.get()); return; - case variant_trait_v: + case VariantType::ArrayInt: mSize = other.mSize; variant_helper::set(&mStore, other.get(), mSize); return; - case variant_trait_v: + case VariantType::ArrayFloat: mSize = other.mSize; variant_helper::set(&mStore, other.get(), mSize); return; - case variant_trait_v: + case VariantType::ArrayDouble: mSize = other.mSize; variant_helper::set(&mStore, other.get(), mSize); return; - case variant_trait_v: + case VariantType::ArrayBool: mSize = other.mSize; variant_helper::set(&mStore, other.get(), mSize); return; - case variant_trait_v: + case VariantType::ArrayString: mSize = other.mSize; - variant_helper::set(&mStore, other.get(), mSize); + variant_helper>::set(&mStore, other.get>()); return; default: mStore = other.mStore; @@ -124,23 +124,14 @@ Variant::Variant(Variant&& other) noexcept : mType(other.mType) mStore = other.mStore; mSize = other.mSize; switch (mType) { - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; - return; - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; - return; - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; - return; - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; - return; - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; + case VariantType::String: + case VariantType::ArrayInt: + case VariantType::ArrayFloat: + case VariantType::ArrayDouble: + case VariantType::ArrayBool: + case VariantType::ArrayString: + *reinterpret_cast(&(other.mStore)) = nullptr; return; - case variant_trait_v: - *reinterpret_cast(&(other.mStore)) = nullptr; default: return; } @@ -151,16 +142,20 @@ Variant::~Variant() // In case we allocated an array, we // should delete it. switch (mType) { - case variant_trait_v: - case variant_trait_v: - case variant_trait_v: - case variant_trait_v: - case variant_trait_v: - case variant_trait_v: + case VariantType::String: + case VariantType::ArrayInt: + case VariantType::ArrayFloat: + case VariantType::ArrayDouble: + case VariantType::ArrayBool: { if (reinterpret_cast(&mStore) != nullptr) { free(*reinterpret_cast(&mStore)); } return; + } + case VariantType::ArrayString: { + // Allocated with placement new. Nothing to delete. + return; + } default: return; } @@ -171,23 +166,23 @@ Variant& Variant::operator=(const Variant& other) mSize = other.mSize; mType = other.mType; switch (mType) { - case variant_trait_v: + case VariantType::String: variant_helper::set(&mStore, other.get()); return *this; - case variant_trait_v: + case VariantType::ArrayInt: variant_helper::set(&mStore, other.get(), mSize); return *this; - case variant_trait_v: + case VariantType::ArrayFloat: variant_helper::set(&mStore, other.get(), mSize); return *this; - case variant_trait_v: + case VariantType::ArrayDouble: variant_helper::set(&mStore, other.get(), mSize); return *this; - case variant_trait_v: + case VariantType::ArrayBool: variant_helper::set(&mStore, other.get(), mSize); return *this; - case variant_trait_v: - variant_helper::set(&mStore, other.get(), mSize); + case VariantType::ArrayString: + variant_helper>::set(&mStore, other.get>()); return *this; default: mStore = other.mStore; @@ -200,29 +195,29 @@ Variant& Variant::operator=(Variant&& other) noexcept mSize = other.mSize; mType = other.mType; switch (mType) { - case variant_trait_v: + case VariantType::String: variant_helper::set(&mStore, other.get()); *reinterpret_cast(&(other.mStore)) = nullptr; return *this; - case variant_trait_v: + case VariantType::ArrayInt: variant_helper::set(&mStore, other.get(), mSize); *reinterpret_cast(&(other.mStore)) = nullptr; return *this; - case variant_trait_v: + case VariantType::ArrayFloat: variant_helper::set(&mStore, other.get(), mSize); *reinterpret_cast(&(other.mStore)) = nullptr; return *this; - case variant_trait_v: + case VariantType::ArrayDouble: variant_helper::set(&mStore, other.get(), mSize); *reinterpret_cast(&(other.mStore)) = nullptr; return *this; - case variant_trait_v: + case VariantType::ArrayBool: variant_helper::set(&mStore, other.get(), mSize); *reinterpret_cast(&(other.mStore)) = nullptr; return *this; - case variant_trait_v: - variant_helper::set(&mStore, other.get(), mSize); - *reinterpret_cast(&(other.mStore)) = nullptr; + case VariantType::ArrayString: + variant_helper>::set(&mStore, other.get>()); + *reinterpret_cast**>(&(other.mStore)) = nullptr; return *this; default: mStore = other.mStore; 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/VariantPropertyTreeHelpers.cxx b/Framework/Core/src/VariantPropertyTreeHelpers.cxx index 2b1746aae2c66..cb0aefaab39ec 100644 --- a/Framework/Core/src/VariantPropertyTreeHelpers.cxx +++ b/Framework/Core/src/VariantPropertyTreeHelpers.cxx @@ -19,17 +19,17 @@ template boost::property_tree::ptree o2::framework::basicVectorToBranch(float*, template boost::property_tree::ptree o2::framework::basicVectorToBranch(int*, size_t); template boost::property_tree::ptree o2::framework::basicVectorToBranch(double*, size_t); template boost::property_tree::ptree o2::framework::basicVectorToBranch(bool*, size_t); -template boost::property_tree::ptree o2::framework::basicVectorToBranch(std::basic_string*, size_t); +template boost::property_tree::ptree o2::framework::basicVectorToBranch(std::basic_string const*, size_t); template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); template boost::property_tree::ptree o2::framework::vectorToBranch(std::vector&& values); -template boost::property_tree::ptree o2::framework::vectorToBranch(float*, size_t); -template boost::property_tree::ptree o2::framework::vectorToBranch(int*, size_t); -template boost::property_tree::ptree o2::framework::vectorToBranch(double*, size_t); -template boost::property_tree::ptree o2::framework::vectorToBranch(bool*, size_t); -template boost::property_tree::ptree o2::framework::vectorToBranch(std::basic_string*, size_t); +template boost::property_tree::ptree o2::framework::vectorToBranch(float const*, size_t); +template boost::property_tree::ptree o2::framework::vectorToBranch(int const*, size_t); +template boost::property_tree::ptree o2::framework::vectorToBranch(double const*, size_t); +template boost::property_tree::ptree o2::framework::vectorToBranch(bool const*, size_t); +template boost::property_tree::ptree o2::framework::vectorToBranch(std::basic_string const*, size_t); template boost::property_tree::ptree o2::framework::labeledArrayToBranch(o2::framework::LabeledArray&& array); template boost::property_tree::ptree o2::framework::labeledArrayToBranch(o2::framework::LabeledArray&& array); diff --git a/Framework/Core/src/WSDriverClient.cxx b/Framework/Core/src/WSDriverClient.cxx index 179b13bf91d76..97ea1b3dbf66a 100644 --- a/Framework/Core/src/WSDriverClient.cxx +++ b/Framework/Core/src/WSDriverClient.cxx @@ -26,6 +26,7 @@ O2_DECLARE_DYNAMIC_LOG(completion); O2_DECLARE_DYNAMIC_LOG(monitoring_service); O2_DECLARE_DYNAMIC_LOG(data_processor_context); O2_DECLARE_DYNAMIC_LOG(stream_context); +O2_DECLARE_DYNAMIC_LOG(ws_client); namespace o2::framework { @@ -49,8 +50,8 @@ struct ClientWebSocketHandler : public WebSocketHandler { mClient.dispatch(std::string_view(frame, s)); } - void endFragmentation() override{}; - void control(char const* frame, size_t s) override{}; + void endFragmentation() override {}; + void control(char const* frame, size_t s) override {}; /// Invoked at the beginning of some incoming data. We simply /// reset actions which need to happen on a per chunk basis. @@ -119,6 +120,34 @@ void on_connect(uv_connect_t* connection, int status) state.pendingOffers.push_back(offer); }); + client->observe("/timeslice-offer", [ref = context->ref](std::string_view cmd) { + O2_SIGNPOST_ID_GENERATE(wid, ws_client); + O2_SIGNPOST_START(ws_client, wid, "timeslice-offer", "Received timeslice offer."); + auto& state = ref.get(); + static constexpr int prefixSize = std::string_view{"/timeslice-offer "}.size(); + if (prefixSize > cmd.size()) { + O2_SIGNPOST_END_WITH_ERROR(ws_client, wid, "timeslice-offer", "Malformed timeslice offer"); + return; + } + cmd.remove_prefix(prefixSize); + int64_t offerSize; + auto offerSizeError = std::from_chars(cmd.data(), cmd.data() + cmd.size(), offerSize); + if (offerSizeError.ec != std::errc()) { + O2_SIGNPOST_END_WITH_ERROR(ws_client, wid, "timeslice-offer", "Unexpected timeslice offer size"); + return; + } + ComputingQuotaOffer offer{ + .cpu = 0, + .memory = 0, + .sharedMemory = 0, + .timeslices = offerSize, + .runtime = 10000, + .user = -1, + .valid = true}; + state.pendingOffers.push_back(offer); + O2_SIGNPOST_END(ws_client, wid, "timeslice-offer", "Received %lli timeslices offer. Total pending offers %zu.", + offerSize, state.pendingOffers.size()); + }); client->observe("/quit", [ref = context->ref](std::string_view) { auto& state = ref.get(); @@ -159,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/WorkflowCustomizationHelpers.cxx b/Framework/Core/src/WorkflowCustomizationHelpers.cxx index 05abb5dab98cd..2154d0fe26f8d 100644 --- a/Framework/Core/src/WorkflowCustomizationHelpers.cxx +++ b/Framework/Core/src/WorkflowCustomizationHelpers.cxx @@ -42,6 +42,7 @@ namespace o2::framework std::vector WorkflowCustomizationHelpers::requiredWorkflowOptions() { return {{{"readers", VariantType::Int64, 1ll, {"number of parallel readers to use"}}, + {"ccdb-fetchers", VariantType::Int64, 1ll, {"number of parallel ccdb-fetchers to use"}}, {"spawners", VariantType::Int64, 1ll, {"number of parallel spawners to use"}}, {"pipeline", VariantType::String, "", {"override default pipeline size"}}, {"clone", VariantType::String, "", {"clone processors from a template"}}, diff --git a/Framework/Core/src/WorkflowHelpers.cxx b/Framework/Core/src/WorkflowHelpers.cxx index 6eda838070f6d..9b80ef14d7621 100644 --- a/Framework/Core/src/WorkflowHelpers.cxx +++ b/Framework/Core/src/WorkflowHelpers.cxx @@ -11,7 +11,6 @@ #include "WorkflowHelpers.h" #include "Framework/AnalysisSupportHelpers.h" #include "Framework/AlgorithmSpec.h" -#include "Framework/AODReaderHelpers.h" #include "Framework/ConfigParamSpec.h" #include "Framework/ConfigParamsHelper.h" #include "Framework/CommonDataProcessors.h" @@ -20,7 +19,6 @@ #include "Framework/DataSpecUtils.h" #include "Framework/DataSpecViews.h" #include "Framework/DataAllocator.h" -#include "Framework/ControlService.h" #include "Framework/RawDeviceService.h" #include "Framework/StringHelpers.h" #include "Framework/ChannelSpecHelpers.h" @@ -38,6 +36,7 @@ #include #include #include +#include O2_DECLARE_DYNAMIC_LOG(workflow_helpers); @@ -101,7 +100,7 @@ std::vector // which have the current node as incoming. // nextEdges will contain all the edges which are not related // to the current node. - for (auto& ei : remainingEdgesIndex) { + for (auto const& ei : remainingEdgesIndex) { if (*(edgeIn + ei * stride) == node.index) { nextVertex.insert({*(edgeOut + ei * stride), node.layer + 1}); } else { @@ -113,7 +112,7 @@ std::vector // Of all the vertices which have node as incoming, // check if there is any other incoming node. std::set hasPredecessors; - for (auto& ei : remainingEdgesIndex) { + for (auto const& ei : remainingEdgesIndex) { for (auto& m : nextVertex) { if (m.index == *(edgeOut + ei * stride)) { hasPredecessors.insert({m.index, m.layer}); @@ -157,18 +156,7 @@ int defaultConditionQueryRateMultiplier() void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext& ctx) { - auto fakeCallback = AlgorithmSpec{[](InitContext& ic) { - LOG(info) << "This is not a real device, merely a placeholder for external inputs"; - LOG(info) << "To be hidden / removed at some point."; - // mark this dummy process as ready-to-quit - ic.services().get().readyToQuit(QuitRequest::Me); - - return [](ProcessingContext& pc) { - // this callback is never called since there is no expiring input - pc.services().get().waitFor(2000); - }; - }}; - + int rateLimitingIPCID = std::stoi(ctx.options().get("timeframes-rate-limit-ipcid")); DataProcessorSpec ccdbBackend{ .name = "internal-dpl-ccdb-backend", .outputs = {}, @@ -177,7 +165,8 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext {"condition-not-after", VariantType::Int64, 3385078236000ll, {"do not fetch from CCDB objects created after the timestamp"}}, {"condition-remap", VariantType::String, "", {"remap condition path in CCDB based on the provided string."}}, {"condition-tf-per-query", VariantType::Int, defaultConditionQueryRate(), {"check condition validity per requested number of TFs, fetch only once if <=0"}}, - {"condition-tf-per-query-multiplier", VariantType::Int, defaultConditionQueryRateMultiplier(), {"check conditions once per this amount of nominal checks"}}, + {"condition-tf-per-query-multiplier", VariantType::Int, defaultConditionQueryRateMultiplier(), {"check conditions once per this amount of nominal checks (>0) or on module of TFcounter (<0)"}}, + {"condition-use-slice-for-prescaling", VariantType::Int, 0, {"use TFslice instead of TFcounter to control validation frequency. If > query rate, do not allow TFCounter excursion exceeding it"}}, {"condition-time-tolerance", VariantType::Int64, 5000ll, {"prefer creation time if its difference to orbit-derived time exceeds threshold (ms), impose if <0"}}, {"orbit-offset-enumeration", VariantType::Int64, 0ll, {"initial value for the orbit"}}, {"orbit-multiplier-enumeration", VariantType::Int64, 0ll, {"multiplier to get the orbit from the counter"}}, @@ -195,7 +184,8 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext {"condition-not-after", VariantType::Int64, 3385078236000ll, {"do not fetch from CCDB objects created after the timestamp"}}, {"condition-remap", VariantType::String, "", {"remap condition path in CCDB based on the provided string."}}, {"condition-tf-per-query", VariantType::Int, defaultConditionQueryRate(), {"check condition validity per requested number of TFs, fetch only once if <=0"}}, - {"condition-tf-per-query-multiplier", VariantType::Int, defaultConditionQueryRateMultiplier(), {"check conditions once per this amount of nominal checks"}}, + {"condition-tf-per-query-multiplier", VariantType::Int, defaultConditionQueryRateMultiplier(), {"check conditions once per this amount of nominal checks (>0) or on module of TFcounter (<0)"}}, + {"condition-use-slice-for-prescaling", VariantType::Int, 0, {"use TFslice instead of TFcounter to control validation frequency. If > query rate, do not allow TFCounter excursion exceeding it"}}, {"condition-time-tolerance", VariantType::Int64, 5000ll, {"prefer creation time if its difference to orbit-derived time exceeds threshold (ms), impose if <0"}}, {"start-value-enumeration", VariantType::Int64, 0ll, {"initial value for the enumeration"}}, {"end-value-enumeration", VariantType::Int64, -1ll, {"final value for the enumeration"}}, @@ -241,25 +231,8 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext ConfigParamSpec{"step-value-enumeration", VariantType::Int64, 1ll, {"step between one value and the other"}}}, .requiredServices = CommonServices::defaultServices("O2FrameworkAnalysisSupport:RunSummary")}; - // AOD reader can be rate limited - int rateLimitingIPCID = std::stoi(ctx.options().get("timeframes-rate-limit-ipcid")); - std::string rateLimitingChannelConfigInput; - std::string rateLimitingChannelConfigOutput; - bool internalRateLimiting = false; - - // In case we have rate-limiting requested, any device without an input will get one on the special - // "DPL/RATE" message. - if (rateLimitingIPCID >= 0) { - rateLimitingChannelConfigInput = fmt::format("name=metric-feedback,type=pull,method=connect,address=ipc://{}metric-feedback-{},transport=shmem,rateLogging=0", - ChannelSpecHelpers::defaultIPCFolder(), rateLimitingIPCID); - rateLimitingChannelConfigOutput = fmt::format("name=metric-feedback,type=push,method=bind,address=ipc://{}metric-feedback-{},transport=shmem,rateLogging=0", - ChannelSpecHelpers::defaultIPCFolder(), rateLimitingIPCID); - internalRateLimiting = true; - aodReader.options.emplace_back(ConfigParamSpec{"channel-config", VariantType::String, rateLimitingChannelConfigInput, {"how many timeframes can be in flight at the same time"}}); - } - - ctx.services().registerService(ServiceRegistryHelpers::handleForService(new AnalysisContext)); - auto& ac = ctx.services().get(); + ctx.services().registerService(ServiceRegistryHelpers::handleForService(new DanglingEdgesContext)); + auto& dec = ctx.services().get(); std::vector requestedCCDBs; std::vector providedCCDBs; @@ -267,8 +240,8 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext for (size_t wi = 0; wi < workflow.size(); ++wi) { auto& processor = workflow[wi]; auto name = processor.name; - auto hash = runtime_hash(name.c_str()); - ac.outTskMap.push_back({hash, name}); + uint32_t hash = runtime_hash(name.c_str()); + dec.outTskMap.push_back({hash, name}); std::string prefix = "internal-dpl-"; if (processor.inputs.empty() && processor.name.compare(0, prefix.size(), prefix) != 0) { @@ -279,37 +252,24 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext processor.options.push_back(ConfigParamSpec{"end-value-enumeration", VariantType::Int64, -1ll, {"final value for the enumeration"}}); processor.options.push_back(ConfigParamSpec{"step-value-enumeration", VariantType::Int64, 1ll, {"step between one value and the other"}}); } - bool hasTimeframeInputs = false; - for (auto& input : processor.inputs) { - if (input.lifetime == Lifetime::Timeframe) { - hasTimeframeInputs = true; - break; - } - } - bool hasTimeframeOutputs = false; - for (auto& output : processor.outputs) { - if (output.lifetime == Lifetime::Timeframe) { - hasTimeframeOutputs = true; - break; - } - } + bool hasTimeframeInputs = std::ranges::any_of(processor.inputs, [](auto const& input) { return input.lifetime == Lifetime::Timeframe; }); + bool hasTimeframeOutputs = std::ranges::any_of(processor.outputs, [](auto const& output) { return output.lifetime == Lifetime::Timeframe; }); + // A timeframeSink consumes timeframes without creating new // timeframe data. bool timeframeSink = hasTimeframeInputs && !hasTimeframeOutputs; - if (std::stoi(ctx.options().get("timeframes-rate-limit-ipcid")) != -1) { + if (rateLimitingIPCID != -1) { if (timeframeSink && processor.name.find("internal-dpl-injected-dummy-sink") == std::string::npos) { O2_SIGNPOST_ID_GENERATE(sid, workflow_helpers); - uint32_t hash = runtime_hash(processor.name.c_str()); bool hasMatch = false; ConcreteDataMatcher summaryMatcher = ConcreteDataMatcher{"DPL", "SUMMARY", static_cast(hash)}; - for (auto& output : processor.outputs) { - if (DataSpecUtils::match(output, summaryMatcher)) { - O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid, "output enumeration", "%{public}s already there in %{public}s", - DataSpecUtils::describe(output).c_str(), processor.name.c_str()); - hasMatch = true; - break; - } + auto summaryOutput = std::ranges::find_if(processor.outputs, [&summaryMatcher](auto const& output) { return DataSpecUtils::match(output, summaryMatcher); }); + if (summaryOutput != processor.outputs.end()) { + O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid, "output enumeration", "%{public}s already there in %{public}s", + DataSpecUtils::describe(*summaryOutput).c_str(), processor.name.c_str()); + hasMatch = true; } + if (!hasMatch) { O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid, "output enumeration", "Adding DPL/SUMMARY/%d to %{public}s", hash, processor.name.c_str()); processor.outputs.push_back(OutputSpec{{"dpl-summary"}, ConcreteDataMatcher{"DPL", "SUMMARY", static_cast(hash)}}); @@ -319,10 +279,28 @@ 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); - auto hasOption = std::any_of(processor.options.begin(), processor.options.end(), [&input](auto const& option) { return (option.name == "period-" + input.binding); }); + auto hasOption = std::ranges::any_of(processor.options, [&input](auto const& option) { return (option.name == "period-" + input.binding); }); if (hasOption == false) { processor.options.push_back(ConfigParamSpec{"period-" + input.binding, VariantType::Int, 1000, {"period of the timer in milliseconds"}}); } @@ -337,22 +315,16 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext timer.outputs.emplace_back(OutputSpec{concrete.origin, concrete.description, concrete.subSpec, Lifetime::Enumeration}); } break; case Lifetime::Condition: { - for (auto& option : processor.options) { - if (option.name == "condition-backend") { - hasConditionOption = true; - break; - } - } - if (hasConditionOption == false) { + requestedCCDBs.emplace_back(input); + if ((hasConditionOption == false) && std::ranges::none_of(processor.options, [](auto const& option) { return (option.name.compare("condition-backend") == 0); })) { processor.options.emplace_back(ConfigParamSpec{"condition-backend", VariantType::String, defaultConditionBackend(), {"URL for CCDB"}}); processor.options.emplace_back(ConfigParamSpec{"condition-timestamp", VariantType::Int64, 0ll, {"Force timestamp for CCDB lookup"}}); hasConditionOption = true; } - requestedCCDBs.emplace_back(input); } break; case Lifetime::OutOfBand: { auto concrete = DataSpecUtils::asConcreteDataMatcher(input); - auto hasOption = std::any_of(processor.options.begin(), processor.options.end(), [&input](auto const& option) { return (option.name == "out-of-band-channel-name-" + input.binding); }); + auto hasOption = std::ranges::any_of(processor.options, [&input](auto const& option) { return (option.name == "out-of-band-channel-name-" + input.binding); }); if (hasOption == false) { processor.options.push_back(ConfigParamSpec{"out-of-band-channel-name-" + input.binding, VariantType::String, "out-of-band", {"channel to listen for out of band data"}}); } @@ -364,38 +336,56 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext case Lifetime::Optional: break; } - if (DataSpecUtils::partialMatch(input, AODOrigins)) { - DataSpecUtils::updateInputList(ac.requestedAODs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"DYN"})) { - DataSpecUtils::updateInputList(ac.requestedDYNs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"IDX"})) { - DataSpecUtils::updateInputList(ac.requestedIDXs, InputSpec{input}); - } - if (DataSpecUtils::partialMatch(input, header::DataOrigin{"ATIM"})) { - DataSpecUtils::updateInputList(ac.requestedTIMs, InputSpec{input}); + if (hasProjectors) { + DataSpecUtils::updateInputList(dec.requestedDYNs, InputSpec{input}); + } else if (hasIndexRecords) { + DataSpecUtils::updateInputList(dec.requestedIDXs, InputSpec{input}); + } else if (hasCCDBURLs) { + DataSpecUtils::updateInputList(dec.requestedTIMs, InputSpec{input}); + } else if (DataSpecUtils::partialMatch(input, AODOrigins)) { + DataSpecUtils::updateInputList(dec.requestedAODs, InputSpec{input}); } } - std::stable_sort(timer.outputs.begin(), timer.outputs.end(), [](OutputSpec const& a, OutputSpec const& b) { return *DataSpecUtils::getOptionalSubSpec(a) < *DataSpecUtils::getOptionalSubSpec(b); }); + 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)) { - ac.providedAODs.emplace_back(output); - } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"DYN"})) { - ac.providedDYNs.emplace_back(output); - } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"ATIM"})) { - ac.providedTIMs.emplace_back(output); + 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 (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"})) { - ac.providedOutputObjHist.emplace_back(output); - auto it = std::find_if(ac.outObjHistMap.begin(), ac.outObjHistMap.end(), [&](auto&& x) { return x.id == hash; }); - if (it == ac.outObjHistMap.end()) { - ac.outObjHistMap.push_back({hash, {output.binding.value}}); + dec.providedOutputObjHist.emplace_back(output); + auto it = std::ranges::find_if(dec.outObjHistMap, [&](auto&& x) { return x.id == hash; }); + if (it == dec.outObjHistMap.end()) { + dec.outObjHistMap.push_back({hash, {output.binding.value}}); } else { it->bindings.push_back(output.binding.value); } } + if (output.lifetime == Lifetime::Condition) { providedCCDBs.push_back(output); } @@ -404,40 +394,41 @@ 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::sort(ac.requestedDYNs.begin(), ac.requestedDYNs.end(), inputSpecLessThan); - std::sort(ac.requestedTIMs.begin(), ac.requestedTIMs.end(), inputSpecLessThan); - std::sort(ac.providedDYNs.begin(), ac.providedDYNs.end(), outputSpecLessThan); - std::sort(ac.providedTIMs.begin(), ac.providedTIMs.end(), outputSpecLessThan); DataProcessorSpec indexBuilder{ "internal-dpl-aod-index-builder", {}, {}, - readers::AODReaderHelpers::indexBuilderCallback(ac.requestedIDXs), + AlgorithmSpec::dummyAlgorithm(), // real algorithm will be set in adjustTopology {}}; - AnalysisSupportHelpers::addMissingOutputsToBuilder(ac.requestedIDXs, ac.requestedAODs, ac.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); - ac.requestedTIMs | views::filter_not_matching(ac.providedTIMs) | sinks::append_to{ac.analysisCCDBInputs}; - DeploymentMode deploymentMode = DefaultsHelpers::deploymentMode(); - if (deploymentMode != DeploymentMode::OnlineDDS && deploymentMode != DeploymentMode::OnlineECS) { - AnalysisSupportHelpers::addMissingOutputsToAnalysisCCDBFetcher({}, ac.analysisCCDBInputs, ac.requestedAODs, ac.requestedTIMs, analysisCCDBBackend); - } + 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}; + AnalysisSupportHelpers::addMissingOutputsToBuilder(dec.analysisCCDBInputs, dec.requestedAODs, dec.requestedDYNs, analysisCCDBBackend); - for (auto& input : ac.requestedDYNs) { - if (std::none_of(ac.providedDYNs.begin(), ac.providedDYNs.end(), [&input](auto const& x) { return DataSpecUtils::match(input, x); })) { - ac.spawnerInputs.emplace_back(input); - } - } + 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{ "internal-dpl-aod-spawner", {}, {}, - readers::AODReaderHelpers::aodSpawnerCallback(ac.spawnerInputs), + AlgorithmSpec::dummyAlgorithm(), // real algorithm will be set in adjustTopology {}}; - AnalysisSupportHelpers::addMissingOutputsToSpawner({}, ac.spawnerInputs, ac.requestedAODs, aodSpawner); + AnalysisSupportHelpers::addMissingOutputsToSpawner({}, dec.spawnerInputs, dec.requestedAODs, aodSpawner); - AnalysisSupportHelpers::addMissingOutputsToReader(ac.providedAODs, ac.requestedAODs, aodReader); + std::ranges::sort(dec.requestedAODs, inputSpecLessThan); + std::ranges::sort(dec.providedAODs, outputSpecLessThan); + AnalysisSupportHelpers::addMissingOutputsToReader(dec.providedAODs, dec.requestedAODs, aodReader); + + std::ranges::sort(requestedCCDBs, inputSpecLessThan); + std::ranges::sort(providedCCDBs, outputSpecLessThan); AnalysisSupportHelpers::addMissingOutputsToReader(providedCCDBs, requestedCCDBs, ccdbBackend); std::vector extraSpecs; @@ -457,27 +448,34 @@ 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 mctracks2aod = std::find_if(workflow.begin(), workflow.end(), [](auto const& x) { return x.name == "mctracks-to-aod"; }); - if (mctracks2aod == workflow.end()) { + if (tfnsource == workflow.end()) { // add normal reader - auto&& algo = PluginManager::loadAlgorithmFromPlugin("O2FrameworkAnalysisSupport", "ROOTFileReader", ctx); - if (internalRateLimiting) { - aodReader.algorithm = CommonDataProcessors::wrapWithRateLimiting(algo); - } else { - aodReader.algorithm = algo; - } aodReader.outputs.emplace_back(OutputSpec{"TFN", "TFNumber"}); aodReader.outputs.emplace_back(OutputSpec{"TFF", "TFFilename"}); } else { - // AODs are being injected on-the-fly, add dummy reader + // AODs are being injected the tfnsource is the entry point, add error-handler reader aodReader.algorithm = AlgorithmSpec{ adaptStateful( - [outputs = aodReader.outputs](DeviceSpec const&) { + [](DeviceSpec const& spec) { LOGP(warn, "Workflow with injected AODs has unsatisfied inputs:"); - for (auto const& output : outputs) { - LOGP(warn, " {}", DataSpecUtils::describe(output)); + for (auto const& output : spec.outputs) { + LOGP(warn, " {}", DataSpecUtils::describe(output.matcher)); } LOGP(fatal, "Stopping."); // to ensure the output type for adaptStateful @@ -489,121 +487,80 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext extraSpecs.push_back(timePipeline(aodReader, ctx.options().get("readers"))); } - ConcreteDataMatcher dstf{"FLP", "DISTSUBTIMEFRAME", 0xccdb}; - if (ccdbBackend.outputs.empty() == false) { - ccdbBackend.outputs.push_back(OutputSpec{"CTP", "OrbitReset", 0}); - InputSpec matcher{"dstf", "FLP", "DISTSUBTIMEFRAME", 0xccdb}; - bool providesDISTSTF = false; - // Check if any of the provided outputs is a DISTSTF - // Check if any of the requested inputs is for a 0xccdb message - for (auto& dp : workflow) { - for (auto& output : dp.outputs) { - if (DataSpecUtils::match(matcher, output)) { - providesDISTSTF = true; - dstf = DataSpecUtils::asConcreteDataMatcher(output); - break; - } - } - if (providesDISTSTF) { - break; - } + InputSpec matcher{"dstf", "FLP", "DISTSUBTIMEFRAME", 0xccdb}; + auto& dstf = std::get(matcher.matcher); + // Check if any of the provided outputs is a DISTSTF + // Check if any of the requested inputs is for a 0xccdb message + bool providesDISTSTF = std::ranges::any_of(workflow, + [&matcher](auto const& dp) { + return std::any_of(dp.outputs.begin(), dp.outputs.end(), [&matcher](auto const& output) { + return DataSpecUtils::match(matcher, output); + }); + }); + + // If there is no CCDB requested, but we still ask for a FLP/DISTSUBTIMEFRAME/0xccdb + // we add to the first data processor which has no inputs (apart from + // enumerations / timers) the responsibility to provide the DISTSUBTIMEFRAME + bool requiresDISTSUBTIMEFRAME = std::ranges::any_of(workflow, + [&dstf](auto const& dp) { + return std::any_of(dp.inputs.begin(), dp.inputs.end(), [&dstf](auto const& input) { + return DataSpecUtils::match(input, dstf); + }); + }); + + // We find the first device which has either just enumerations or + // just timers, and we will add the DISTSUBTIMEFRAME to it. + // Notice how we do so in a stable manner by sorting the devices + // by name. + int enumCandidate = -1; + int timerCandidate = -1; + for (auto wi = 0U; wi < workflow.size(); ++wi) { + auto& dp = workflow[wi]; + if (dp.inputs.size() != 1) { + continue; + } + auto lifetime = dp.inputs[0].lifetime; + if (lifetime == Lifetime::Enumeration && (enumCandidate == -1 || workflow[enumCandidate].name > dp.name)) { + enumCandidate = wi; + } + if (lifetime == Lifetime::Timer && (timerCandidate == -1 || workflow[timerCandidate].name > dp.name)) { + timerCandidate = wi; } - // * If there are AOD outputs we use TFNumber as the CCDB clock - // * If one device provides a DISTSTF we use that as the CCDB clock - // * If one of the devices provides a timer we use that as the CCDB clock - // * If none of the above apply add to the first data processor - // which has no inputs apart from enumerations the responsibility - // to provide the DISTSUBTIMEFRAME. + } + + // * If there are AOD outputs we use TFNumber as the CCDB clock + // * If one device provides a DISTSTF we use that as the CCDB clock + // * If one of the devices provides a timer we use that as the CCDB clock + // * If none of the above apply, add to the first data processor + // which has no inputs apart from enumerations the responsibility + // to provide the DISTSUBTIMEFRAME. + if (ccdbBackend.outputs.empty() == false) { if (aodReader.outputs.empty() == false) { + // fetcher clock follows AOD source (TFNumber) ccdbBackend.inputs.push_back(InputSpec{"tfn", "TFN", "TFNumber"}); } else if (providesDISTSTF) { + // fetcher clock follows DSTF/ccdb source (DISTSUBTIMEFRAME) ccdbBackend.inputs.push_back(InputSpec{"tfn", dstf, Lifetime::Timeframe}); } else { - // We find the first device which has either just enumerations or - // just timers, and we add the DISTSUBTIMEFRAME to it. - // Notice how we do so in a stable manner by sorting the devices - // by name. - int enumCandidate = -1; - int timerCandidate = -1; - for (size_t wi = 0; wi < workflow.size(); wi++) { - auto& dp = workflow[wi]; - if (dp.inputs.size() != 1) { - continue; - } - auto lifetime = dp.inputs[0].lifetime; - if (lifetime == Lifetime::Enumeration && (enumCandidate == -1 || workflow[enumCandidate].name > dp.name)) { - enumCandidate = wi; - } - if (lifetime == Lifetime::Timer && (timerCandidate == -1 || workflow[timerCandidate].name > dp.name)) { - timerCandidate = wi; - } - } if (enumCandidate != -1) { - auto& dp = workflow[enumCandidate]; - DataSpecUtils::updateOutputList(dp.outputs, OutputSpec{{"ccdb-diststf"}, dstf, Lifetime::Timeframe}); + // add DSTF/ccdb source to the enumeration-driven source explicitly + // fetcher clock is provided by enumeration-driven source (DISTSUBTIMEFRAME) + DataSpecUtils::updateOutputList(workflow[enumCandidate].outputs, OutputSpec{{"ccdb-diststf"}, dstf, Lifetime::Timeframe}); ccdbBackend.inputs.push_back(InputSpec{"tfn", dstf, Lifetime::Timeframe}); } else if (timerCandidate != -1) { - auto& dp = workflow[timerCandidate]; - dstf = DataSpecUtils::asConcreteDataMatcher(dp.outputs[0]); - ccdbBackend.inputs.push_back(InputSpec{{"tfn"}, dstf, Lifetime::Timeframe}); + // fetcher clock is proived by timer source + auto timer_dstf = DataSpecUtils::asConcreteDataMatcher(workflow[timerCandidate].outputs[0]); + ccdbBackend.inputs.push_back(InputSpec{"tfn", timer_dstf, Lifetime::Timeframe}); } } + ccdbBackend.outputs.push_back(OutputSpec{"CTP", "OrbitReset", 0}); // Load the CCDB backend from the plugin ccdbBackend.algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "CCDBFetcherPlugin", ctx); - extraSpecs.push_back(ccdbBackend); - } else { - // If there is no CCDB requested, but we still ask for a FLP/DISTSUBTIMEFRAME/0xccdb - // we add to the first data processor which has no inputs (apart from - // enumerations / timers) the responsibility to provide the DISTSUBTIMEFRAME - bool requiresDISTSUBTIMEFRAME = false; - for (auto& dp : workflow) { - for (auto& input : dp.inputs) { - if (DataSpecUtils::match(input, dstf)) { - requiresDISTSUBTIMEFRAME = true; - break; - } - } - } - if (requiresDISTSUBTIMEFRAME) { - // We find the first device which has either just enumerations or - // just timers, and we add the DISTSUBTIMEFRAME to it. - // Notice how we do so in a stable manner by sorting the devices - // by name. - int enumCandidate = -1; - int timerCandidate = -1; - for (size_t wi = 0; wi < workflow.size(); wi++) { - auto& dp = workflow[wi]; - if (dp.inputs.size() != 1) { - continue; - } - auto lifetime = dp.inputs[0].lifetime; - if (lifetime == Lifetime::Enumeration && (enumCandidate == -1 || workflow[enumCandidate].name > dp.name)) { - enumCandidate = wi; - } - if (lifetime == Lifetime::Timer && (timerCandidate == -1 || workflow[timerCandidate].name > dp.name)) { - timerCandidate = wi; - } - } - if (enumCandidate != -1) { - auto& dp = workflow[enumCandidate]; - DataSpecUtils::updateOutputList(dp.outputs, OutputSpec{{"ccdb-diststf"}, dstf, Lifetime::Timeframe}); - ccdbBackend.inputs.push_back(InputSpec{"tfn", dstf, Lifetime::Timeframe}); - } else if (timerCandidate != -1) { - auto& dp = workflow[timerCandidate]; - dstf = DataSpecUtils::asConcreteDataMatcher(dp.outputs[0]); - ccdbBackend.inputs.push_back(InputSpec{{"tfn"}, dstf, Lifetime::Timeframe}); - } - } - } - - // add the Analysys CCDB backend which reads CCDB objects using a provided - // table - if (analysisCCDBBackend.outputs.empty() == false) { - // add normal reader - auto&& algo = PluginManager::loadAlgorithmFromPlugin("O2FrameworkCCDBSupport", "AnalysisCCDBFetcherPlugin", ctx); - analysisCCDBBackend.algorithm = algo; - extraSpecs.push_back(analysisCCDBBackend); + extraSpecs.push_back(timePipeline(ccdbBackend, ctx.options().get("ccdb-fetchers"))); + } else if (requiresDISTSUBTIMEFRAME && enumCandidate != -1) { + // add DSTF/ccdb source to the enumeration-driven source explicitly if it is required in the workflow + DataSpecUtils::updateOutputList(workflow[enumCandidate].outputs, OutputSpec{{"ccdb-diststf"}, dstf, Lifetime::Timeframe}); } // add the timer @@ -613,7 +570,7 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext // This is to inject a file sink so that any dangling ATSK object is written // to a ROOT file. - if (ac.providedOutputObjHist.empty() == false) { + if (dec.providedOutputObjHist.empty() == false) { auto rootSink = AnalysisSupportHelpers::getOutputObjHistSink(ctx); extraSpecs.push_back(rootSink); } @@ -621,64 +578,34 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end()); extraSpecs.clear(); - /// Analyze all ouputs - auto [outputsInputsTmp, isDanglingTmp] = analyzeOutputs(workflow); - ac.isDangling = isDanglingTmp; - ac.outputsInputs = outputsInputsTmp; - - // create DataOutputDescriptor - std::shared_ptr dod = AnalysisSupportHelpers::getDataOutputDirector(ctx); - - // select outputs of type AOD which need to be saved - // ATTENTION: if there are dangling outputs the getGlobalAODSink - // has to be created in any case! - for (auto ii = 0u; ii < ac.outputsInputs.size(); ii++) { - if (DataSpecUtils::partialMatch(ac.outputsInputs[ii], extendedAODOrigins)) { - auto ds = dod->getDataOutputDescriptors(ac.outputsInputs[ii]); - if (ds.size() > 0 || ac.isDangling[ii]) { - ac.outputsInputsAOD.emplace_back(ac.outputsInputs[ii]); - } - } - } - - // file sink for any AOD output - if (ac.outputsInputsAOD.size() > 0) { - // add TFNumber and TFFilename as input to the writer - ac.outputsInputsAOD.emplace_back(InputSpec{"tfn", "TFN", "TFNumber"}); - ac.outputsInputsAOD.emplace_back(InputSpec{"tff", "TFF", "TFFilename"}); - auto fileSink = AnalysisSupportHelpers::getGlobalAODSink(ctx); - extraSpecs.push_back(fileSink); - - auto it = std::find_if(ac.outputsInputs.begin(), ac.outputsInputs.end(), [](InputSpec& spec) -> bool { - return DataSpecUtils::partialMatch(spec, o2::header::DataOrigin("TFN")); - }); - size_t ii = std::distance(ac.outputsInputs.begin(), it); - ac.isDangling[ii] = false; - } - - workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end()); - extraSpecs.clear(); + injectAODWriter(workflow, ctx); // Select dangling outputs which are not of type AOD std::vector redirectedOutputsInputs; - for (auto ii = 0u; ii < ac.outputsInputs.size(); ii++) { + for (auto ii = 0u; ii < dec.outputsInputs.size(); ii++) { if (ctx.options().get("forwarding-policy") == "none") { continue; } // We forward to the output proxy all the inputs only if they are dangling // or if the forwarding policy is "proxy". - if (!ac.isDangling[ii] && (ctx.options().get("forwarding-policy") != "all")) { + if (!dec.isDangling[ii] && (ctx.options().get("forwarding-policy") != "all")) { continue; } // AODs are skipped in any case. - if (DataSpecUtils::partialMatch(ac.outputsInputs[ii], extendedAODOrigins)) { + if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], AODOrigins)) { continue; } - redirectedOutputsInputs.emplace_back(ac.outputsInputs[ii]); + redirectedOutputsInputs.emplace_back(dec.outputsInputs[ii]); } 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()) { @@ -690,14 +617,32 @@ 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) { ignoredInput.lifetime = Lifetime::Sporadic; } - extraSpecs.push_back(CommonDataProcessors::getDummySink(ignored, rateLimitingChannelConfigOutput)); + // Use the new dummy sink when the AOD reader is there + O2_SIGNPOST_ID_GENERATE(sid, workflow_helpers); + 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 { // 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) { + rateLimitingChannelConfigOutput = fmt::format("name=metric-feedback,type=push,method=bind,address=ipc://{}metric-feedback-{},transport=shmem,rateLogging=0", ChannelSpecHelpers::defaultIPCFolder(), rateLimitingIPCID); + } + extraSpecs.push_back(CommonDataProcessors::getDummySink(ignored, rateLimitingChannelConfigOutput)); + } } workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end()); @@ -771,15 +716,8 @@ void WorkflowHelpers::adjustTopology(WorkflowSpec& workflow, ConfigContext const } if (distSTFCount > 0) { - bool found = false; for (auto& spec : workflow) { - for (auto& output : spec.outputs) { - if (DataSpecUtils::match(output, ConcreteDataMatcher{"FLP", "DISTSUBTIMEFRAME", 0})) { - found = true; - break; - } - } - if (found) { + if (std::ranges::any_of(spec.outputs, [](auto const& output) { return DataSpecUtils::match(output, ConcreteDataMatcher{"FLP", "DISTSUBTIMEFRAME", 0}); })) { for (unsigned int i = 1; i < distSTFCount; ++i) { spec.outputs.emplace_back(OutputSpec{ConcreteDataMatcher{"FLP", "DISTSUBTIMEFRAME", i}, Lifetime::Timeframe}); } @@ -789,6 +727,46 @@ void WorkflowHelpers::adjustTopology(WorkflowSpec& workflow, ConfigContext const } } +void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext const& ctx) +{ + auto& dec = ctx.services().get(); + /// Analyze all ouputs + std::tie(dec.outputsInputs, dec.isDangling) = analyzeOutputs(workflow); + + // create DataOutputDescriptor + std::shared_ptr dod = AnalysisSupportHelpers::getDataOutputDirector(ctx); + + // 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], AODOrigins)) { + auto ds = dod->getDataOutputDescriptors(dec.outputsInputs[ii]); + if (ds.size() > 0 || dec.isDangling[ii]) { + dec.outputsInputsAOD.emplace_back(dec.outputsInputs[ii]); + } + } + } + + // file sink for any AOD output + if (dec.outputsInputsAOD.size() > 0) { + // add TFNumber and TFFilename as input to the writer + DataSpecUtils::updateInputList(dec.outputsInputsAOD, InputSpec{"tfn", "TFN", "TFNumber"}); + DataSpecUtils::updateInputList(dec.outputsInputsAOD, InputSpec{"tff", "TFF", "TFFilename"}); + auto fileSink = AnalysisSupportHelpers::getGlobalAODSink(ctx); + workflow.push_back(fileSink); + + auto it = std::find_if(dec.outputsInputs.begin(), dec.outputsInputs.end(), [](InputSpec const& spec) -> bool { + return DataSpecUtils::partialMatch(spec, o2::header::DataOrigin("TFN")); + }); + dec.isDangling[std::distance(dec.outputsInputs.begin(), it)] = false; + + it = std::find_if(dec.outputsInputs.begin(), dec.outputsInputs.end(), [](InputSpec const& spec) -> bool { + return DataSpecUtils::partialMatch(spec, o2::header::DataOrigin("TFF")); + }); + dec.isDangling[std::distance(dec.outputsInputs.begin(), it)] = false; + } +} + void WorkflowHelpers::constructGraph(const WorkflowSpec& workflow, std::vector& logicalEdges, std::vector& outputs, @@ -894,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", ""); } @@ -976,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; @@ -1082,7 +1058,7 @@ std::tuple, std::vector> WorkflowHelpers::analyzeOu input.binding = (snprintf(buf, 63, "output_%zu_%zu", output.workflowId, output.id), buf); // make sure that entries are unique - if (std::find(results.begin(), results.end(), input) == results.end()) { + if (std::ranges::find(results, input) == results.end()) { results.emplace_back(input); isDangling.emplace_back(matched == false); } @@ -1164,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/WorkflowHelpers.h b/Framework/Core/src/WorkflowHelpers.h index b2a4d4cab55df..5c0aa363c6d67 100644 --- a/Framework/Core/src/WorkflowHelpers.h +++ b/Framework/Core/src/WorkflowHelpers.h @@ -182,6 +182,9 @@ struct WorkflowHelpers { // @a ctx the context for the configuration phase static void injectServiceDevices(WorkflowSpec& workflow, ConfigContext& ctx); + // Function to correctly add AOD writer + static void injectAODWriter(WorkflowSpec& workflow, ConfigContext const& ctx); + // Final adjustments to @a workflow after service devices have been injected. static void adjustTopology(WorkflowSpec& workflow, ConfigContext const& ctx); diff --git a/Framework/Core/src/WorkflowSerializationHelpers.cxx b/Framework/Core/src/WorkflowSerializationHelpers.cxx index e20e23f98c90b..b824e8d0bb424 100644 --- a/Framework/Core/src/WorkflowSerializationHelpers.cxx +++ b/Framework/Core/src/WorkflowSerializationHelpers.cxx @@ -29,6 +29,7 @@ #include O2_DECLARE_DYNAMIC_LOG(workflow_importer); +O2_DECLARE_DYNAMIC_LOG(post_workflow_importer); namespace o2::framework { @@ -429,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}; @@ -969,7 +971,18 @@ bool WorkflowSerializationHelpers::import(std::istream& s, WorkflowImporter importer{workflow, metadata, command}; bool ok = reader.Parse(isw, importer); if (ok == false) { - throw std::runtime_error("Error while parsing serialised workflow"); + if (s.eof()) { + throw std::runtime_error("Error while parsing serialised workflow"); + } + // clean up possible leftovers at the end of the input stream, e.g. [DEBUG] message from destructors + O2_SIGNPOST_ID_GENERATE(sid, post_workflow_importer); + while (true) { + s.getline(buf, 1024, '\n'); + if (s.eof()) { + break; + } + O2_SIGNPOST_EVENT_EMIT(post_workflow_importer, sid, "post import", "Following leftover line found in input stream after parsing workflow: %{public}s", buf); + } } return true; } diff --git a/Framework/Core/src/runDataProcessing.cxx b/Framework/Core/src/runDataProcessing.cxx index 59bacc67fef31..c58f8e7287b3b 100644 --- a/Framework/Core/src/runDataProcessing.cxx +++ b/Framework/Core/src/runDataProcessing.cxx @@ -9,6 +9,7 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. #include +#include "Framework/DanglingEdgesContext.h" #include "Framework/TopologyPolicyHelpers.h" #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include @@ -67,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" @@ -748,7 +750,11 @@ void spawnDevice(uv_loop_t* loop, for (auto& env : execution.environ) { putenv(strdup(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec).data())); } - execvp(execution.args[0], execution.args.data()); + int err = execvp(execution.args[0], execution.args.data()); + if (err) { + perror("Unable to start child process"); + exit(1); + } } else { O2_SIGNPOST_ID_GENERATE(sid, driver); O2_SIGNPOST_EVENT_EMIT(driver, sid, "spawnDevice", "New child at %{pid}d", id); @@ -812,7 +818,8 @@ void spawnDevice(uv_loop_t* loop, .sendInitialValue = true, }); - for (size_t i = 0; i < DefaultsHelpers::pipelineLength(); ++i) { + unsigned int pipelineLength = DefaultsHelpers::pipelineLength(DeviceConfig{varmap}); + for (size_t i = 0; i < pipelineLength; ++i) { allStates.back().registerState(DataProcessingStates::StateSpec{ .name = fmt::format("matcher_variables/{}", i), .stateId = static_cast((short)(ProcessingStateId::CONTEXT_VARIABLES_BASE) + i), @@ -821,7 +828,7 @@ void spawnDevice(uv_loop_t* loop, }); } - for (size_t i = 0; i < DefaultsHelpers::pipelineLength(); ++i) { + for (size_t i = 0; i < pipelineLength; ++i) { allStates.back().registerState(DataProcessingStates::StateSpec{ .name = fmt::format("data_relayer/{}", i), .stateId = static_cast((short)(ProcessingStateId::DATA_RELAYER_BASE) + i), @@ -885,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 @@ -1012,6 +1020,7 @@ void doDefaultWorkflowTerminationHook() } int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, + DanglingEdgesContext& danglingEdgesContext, RunningWorkflowInfo const& runningWorkflow, RunningDeviceRef ref, DriverConfig const& driverConfig, @@ -1022,7 +1031,7 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, fair::Logger::SetConsoleColor(false); fair::Logger::OnFatal([]() { throw runtime_error("Fatal error"); }); DeviceSpec const& spec = runningWorkflow.devices[ref.index]; - LOG(info) << "Spawing new device " << spec.id << " in process with pid " << getpid(); + LOG(info) << "Spawning new device " << spec.id << " in process with pid " << getpid(); fair::mq::DeviceRunner runner{argc, argv}; @@ -1038,7 +1047,7 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, defaultDataProcessingTimeout = "20"; defaultInfologgerMode = "infoLoggerD"; } else if (deploymentMode == o2::framework::DeploymentMode::OnlineECS) { - defaultExitTransitionTimeout = "25"; + defaultExitTransitionTimeout = "40"; defaultDataProcessingTimeout = "20"; } boost::program_options::options_description optsDesc; @@ -1052,8 +1061,9 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, ("signposts", bpo::value()->default_value(defaultSignposts ? defaultSignposts : ""), "comma separated list of signposts to enable") // ("expected-region-callbacks", bpo::value()->default_value("0"), "how many region callbacks we are expecting") // ("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); @@ -1073,6 +1083,7 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, &spec, "aEvaluator, &serviceRegistry, + &danglingEdgesContext, &deviceState, &deviceProxy, &processingPolicies, @@ -1091,13 +1102,14 @@ int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry, quotaEvaluator = std::make_unique(serviceRef); serviceRef.registerService(ServiceRegistryHelpers::handleForService(quotaEvaluator.get())); - deviceContext = std::make_unique(); + deviceContext = std::make_unique(DeviceContext{.processingPolicies = processingPolicies}); serviceRef.registerService(ServiceRegistryHelpers::handleForService(&spec)); serviceRef.registerService(ServiceRegistryHelpers::handleForService(&runningWorkflow)); serviceRef.registerService(ServiceRegistryHelpers::handleForService(deviceContext.get())); serviceRef.registerService(ServiceRegistryHelpers::handleForService(&driverConfig)); + serviceRef.registerService(ServiceRegistryHelpers::handleForService(&danglingEdgesContext)); - auto device = std::make_unique(ref, serviceRegistry, processingPolicies); + auto device = std::make_unique(ref, serviceRegistry); serviceRef.get().setDevice(device.get()); r.fDevice = std::move(device); @@ -1233,10 +1245,12 @@ std::vector getDumpableMetrics() dumpableMetrics.emplace_back("^aod-bytes-read-uncompressed$"); dumpableMetrics.emplace_back("^aod-bytes-read-compressed$"); dumpableMetrics.emplace_back("^aod-file-read-info$"); + dumpableMetrics.emplace_back("^aod-largest-object-written$"); dumpableMetrics.emplace_back("^table-bytes-.*"); dumpableMetrics.emplace_back("^total-timeframes.*"); dumpableMetrics.emplace_back("^device_state.*"); dumpableMetrics.emplace_back("^total_wall_time_ms$"); + dumpableMetrics.emplace_back("^ccdb-.*$"); return dumpableMetrics; } @@ -1245,8 +1259,10 @@ void dumpMetricsCallback(uv_timer_t* handle) auto* context = (DriverServerContext*)handle->data; static auto performanceMetrics = getDumpableMetrics(); + std::ofstream file(context->driver->resourcesMonitoringFilename, std::ios::out); ResourcesMonitoringHelper::dumpMetricsToJSON(*(context->metrics), - context->driver->metrics, *(context->specs), performanceMetrics); + context->driver->metrics, *(context->specs), performanceMetrics, + file); } void dumpRunSummary(DriverServerContext& context, DriverInfo const& driverInfo, DeviceInfos const& infos, DeviceSpecs const& specs) @@ -1416,6 +1432,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, // We initialise this in the driver, because different drivers might have // different versions of the service ServiceRegistry serviceRegistry; + ServiceRegistryRef::globalDeviceRef(new ServiceRegistryRef{serviceRegistry, ServiceRegistry::globalDeviceSalt()}); if ((driverConfig.batch == false || getenv("DPL_DRIVER_REMOTE_GUI") != nullptr) && frameworkId.empty()) { debugGUI = initDebugGUI(); @@ -1526,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) { @@ -1663,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; }); }); @@ -1945,6 +1967,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, if (runningWorkflow.devices[di].id == frameworkId) { return doChild(driverInfo.argc, driverInfo.argv, serviceRegistry, + driverInfo.configContext->services().get(), runningWorkflow, ref, driverConfig, driverInfo.processingPolicies, @@ -2029,10 +2052,13 @@ 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", "--time-limit", }; @@ -2201,8 +2227,11 @@ int runStateMachine(DataProcessorSpecs const& workflow, driverInfo.states.push_back(DriverState::RUNNING); } break; - case DriverState::QUIT_REQUESTED: - LOG(info) << "QUIT_REQUESTED"; + case DriverState::QUIT_REQUESTED: { + std::time_t result = std::time(nullptr); + char buffer[32]; + std::strncpy(buffer, std::ctime(&result), 26); + O2_SIGNPOST_EVENT_EMIT_INFO(driver, sid, "mainloop", "Quit requested at %{public}s", buffer); guiQuitRequested = true; // We send SIGCONT to make sure stopped children are resumed killChildren(infos, SIGCONT); @@ -2214,6 +2243,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, uv_timer_start(&force_step_timer, single_step_callback, 0, 300); driverInfo.states.push_back(DriverState::HANDLE_CHILDREN); break; + } case DriverState::HANDLE_CHILDREN: { // Run any pending libUV event loop, block if // any, so that we do not consume CPU time when the driver is @@ -2262,7 +2292,7 @@ int runStateMachine(DataProcessorSpecs const& workflow, if (driverInfo.resourcesMonitoringDumpInterval) { uv_timer_stop(&metricDumpTimer); } - LOG(info) << "Dumping performance metrics to performanceMetrics.json file"; + LOGP(info, "Dumping performance metrics to {}.json file", driverInfo.resourcesMonitoringFilename); dumpMetricsCallback(&metricDumpTimer); } dumpRunSummary(serverContext, driverInfo, infos, runningWorkflow.devices); @@ -2910,6 +2940,7 @@ int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow, ("no-IPC", bpo::value()->zero_tokens()->default_value(false), "disable IPC topology optimization") // // ("o2-control,o2", bpo::value()->default_value(""), "dump O2 Control workflow configuration under the specified name") // ("resources-monitoring", bpo::value()->default_value(0), "enable cpu/memory monitoring for provided interval in seconds") // + ("resources-monitoring-file", bpo::value()->default_value("performanceMetrics.json"), "file where to dump the metrics") // ("resources-monitoring-dump-interval", bpo::value()->default_value(0), "dump monitoring information to disk every provided seconds"); // // some of the options must be forwarded by default to the device executorOptions.add(DeviceSpecHelpers::getForwardedDeviceOptions()); @@ -2991,8 +3022,8 @@ int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow, ServiceSpecs driverServices = ServiceSpecHelpers::filterDisabled(CommonDriverServices::defaultServices(), driverServicesOverride); // We insert the hash for the internal devices. WorkflowHelpers::injectServiceDevices(physicalWorkflow, configContext); - auto reader = std::find_if(physicalWorkflow.begin(), physicalWorkflow.end(), [](DataProcessorSpec& spec) { return spec.name == "internal-dpl-aod-reader"; }); - if (reader != physicalWorkflow.end()) { + auto& dec = configContext.services().get(); + if (!(dec.requestedAODs.empty() && dec.requestedDYNs.empty() && dec.requestedIDXs.empty() && dec.requestedTIMs.empty())) { driverServices.push_back(ArrowSupport::arrowBackendSpec()); } for (auto& service : driverServices) { @@ -3180,6 +3211,7 @@ int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow, driverInfo.deployHostname = varmap["hostname"].as(); driverInfo.resources = varmap["resources"].as(); driverInfo.resourcesMonitoringInterval = varmap["resources-monitoring"].as(); + driverInfo.resourcesMonitoringFilename = varmap["resources-monitoring-file"].as(); driverInfo.resourcesMonitoringDumpInterval = varmap["resources-monitoring-dump-interval"].as(); // FIXME: should use the whole dataProcessorInfos, actually... diff --git a/Framework/Core/test/benchmark_DataRelayer.cxx b/Framework/Core/test/benchmark_DataRelayer.cxx index dcff3930dbaad..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}); + 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}); + 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}); + 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}); + 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}); + 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_EventMixing.cxx b/Framework/Core/test/benchmark_EventMixing.cxx index 99a7d0d4b1cb9..0e7e6839ee35e 100644 --- a/Framework/Core/test/benchmark_EventMixing.cxx +++ b/Framework/Core/test/benchmark_EventMixing.cxx @@ -78,7 +78,8 @@ static void BM_EventMixingTraditional(benchmark::State& state) auto tableTrack = trackBuilder.finalize(); o2::aod::StoredTracks tracks{tableTrack}; - ArrowTableSlicingCache atscache({{getLabelFromType(), "fIndex" + cutString(getLabelFromType())}}); + std::string key = "fIndex" + cutString(getLabelFromType()); + ArrowTableSlicingCache atscache({{getLabelFromType(), getMatcherFromTypeForKey(key), key}}); auto s = atscache.updateCacheEntry(0, tableTrack); SliceCache cache{&atscache}; @@ -171,7 +172,8 @@ static void BM_EventMixingCombinations(benchmark::State& state) int64_t count = 0; int64_t colCount = 0; - ArrowTableSlicingCache atscache{{{getLabelFromType(), "fIndex" + getLabelFromType()}}}; + std::string key = "fIndex" + getLabelFromType(); + ArrowTableSlicingCache atscache{{{getLabelFromType(), getMatcherFromTypeForKey(key), key}}}; auto s = atscache.updateCacheEntry(0, tableTrack); SliceCache cache{&atscache}; 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_ASoA.cxx b/Framework/Core/test/test_ASoA.cxx index 80519aebc9ee7..117dddff4c548 100644 --- a/Framework/Core/test/test_ASoA.cxx +++ b/Framework/Core/test/test_ASoA.cxx @@ -1187,7 +1187,8 @@ TEST_CASE("TestSliceByCached") auto refs = w.finalize(); o2::aod::References r{refs}; - ArrowTableSlicingCache atscache({{o2::soa::getLabelFromType(), "fIndex" + o2::framework::cutString(o2::soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(o2::soa::getLabelFromType()); + ArrowTableSlicingCache atscache({{o2::soa::getLabelFromType(), o2::soa::getMatcherFromTypeForKey(key), key}}); auto s = atscache.updateCacheEntry(0, refs); SliceCache cache{&atscache}; @@ -1238,7 +1239,7 @@ TEST_CASE("TestSliceByCachedMismatched") J rr{{refs, refs2}}; auto key = "fIndex" + o2::framework::cutString(o2::soa::getLabelFromType()) + "_alt"; - ArrowTableSlicingCache atscache({{o2::soa::getLabelFromTypeForKey(key), key}}); + ArrowTableSlicingCache atscache({{o2::soa::getLabelFromTypeForKey(key), o2::soa::getMatcherFromTypeForKey(key), key}}); auto s = atscache.updateCacheEntry(0, refs2); SliceCache cache{&atscache}; diff --git a/Framework/Core/test/test_AllCrashTypes.sh b/Framework/Core/test/test_AllCrashTypes.sh index 54898fd9c4c5d..d333cf4252816 100755 --- a/Framework/Core/test/test_AllCrashTypes.sh +++ b/Framework/Core/test/test_AllCrashTypes.sh @@ -1,23 +1,23 @@ #!/bin/sh -e echo $PATH printf "ok\nTesting runtime-init..." -o2-framework-crashing-workflow --crash-type=runtime-init --completion-policy=quit -b --run | grep -q "Exception caught while in Init: This is a std::runtime_error. Exiting with 1." || { printf "runtime error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=runtime-init --completion-policy=quit -b --run | tee error.log | grep -q "Exception caught while in Init: This is a std::runtime_error. Exiting with 1." || { printf "runtime error not found" ; cat error.log ; exit 1; } printf "ok\nTesting framework-init..." -o2-framework-crashing-workflow --crash-type=framework-init --completion-policy=quit -b --run | grep -q "Exception caught while in Init: This is a o2::framework::runtime_error. Exiting with 1." || { printf "framework error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=framework-init --completion-policy=quit -b --run | tee error.log | grep -q "Exception caught while in Init: This is a o2::framework::runtime_error. Exiting with 1." || { printf "framework error not found" ; cat error.log ; exit 1; } printf "ok\nTesting framework-run..." -o2-framework-crashing-workflow --crash-type=framework-run --completion-policy=quit -b --run | grep -q "Unhandled o2::framework::runtime_error reached the top of main of o2-framework-crashing-workflow, device shutting down. Reason: This is a o2::framework::runtime_error" || { printf "framework error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=framework-run --completion-policy=quit -b --run | tee error.log | grep -q "Unhandled o2::framework::runtime_error reached the top of main of [^ ]*o2-framework-crashing-workflow, device shutting down. Reason: This is a o2::framework::runtime_error" || { printf "framework error not found" ; cat error.log ; exit 1; } printf "ok\nTesting runtime-run..." -o2-framework-crashing-workflow --crash-type=runtime-run --completion-policy=quit --run | grep -q "Unhandled o2::framework::runtime_error reached the top of main of o2-framework-crashing-workflow, device shutting down. Reason: This is a std::runtime_error" || { echo "runtime error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=runtime-run --completion-policy=quit --run | tee error.log | grep -q "Unhandled o2::framework::runtime_error reached the top of main of [^ ]*o2-framework-crashing-workflow, device shutting down. Reason: This is a std::runtime_error" || { echo "runtime error not found" ; cat error.log ; exit 1; } printf "ok\n" export O2_NO_CATCHALL_EXCEPTIONS=1 echo O2_NO_CATCHALL_EXCEPTIONS enabled printf "ok\nTesting runtime-init..." -o2-framework-crashing-workflow --crash-type=runtime-init --completion-policy=quit -b --run | grep -v -q "Exception caught: This is a std::runtime_error" || { printf "runtime error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=runtime-init --completion-policy=quit -b --run | tee error.log | grep -v -q "Exception caught: This is a std::runtime_error" || { printf "runtime error not found" ; cat error.log ; exit 1; } printf "ok\nTesting framework-init..." -o2-framework-crashing-workflow --crash-type=framework-init --completion-policy=quit -b --run | grep -v -q "Exception caught: This is a o2::framework::runtime_error" || { printf "framework error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=framework-init --completion-policy=quit -b --run | tee error.log | grep -v -q "Exception caught: This is a o2::framework::runtime_error" || { printf "framework error not found" ; cat error.log ; exit 1; } printf "ok\nTesting framework-run..." -o2-framework-crashing-workflow --crash-type=framework-run --completion-policy=quit -b --run | grep -v -q "Unhandled o2::framework::runtime_error reached the top of main of o2-framework-crashing-workflow, device shutting down. Reason: This is a o2::framework::runtime_error" || { printf "framework error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=framework-run --completion-policy=quit -b --run | tee error.log | grep -v -q "Unhandled o2::framework::runtime_error reached the top of main of [^ ]*o2-framework-crashing-workflow, device shutting down. Reason: This is a o2::framework::runtime_error" || { printf "framework error not found" ; cat error.log ; exit 1; } printf "ok\nTesting runtime-run..." -o2-framework-crashing-workflow --crash-type=runtime-run --completion-policy=quit --run | grep -v -q "Unhandled o2::framework::runtime_error reached the top of main of o2-framework-crashing-workflow, device shutting down. Reason: This is a std::runtime_error" || { echo "runtime error not found" ; exit 1; } +o2-framework-crashing-workflow --crash-type=runtime-run --completion-policy=quit --run | tee error.log | grep -v -q "Unhandled o2::framework::runtime_error reached the top of main of [^ ]*o2-framework-crashing-workflow, device shutting down. Reason: This is a std::runtime_error" || { echo "runtime error not found"; cat error.log ; exit 1; } printf "ok" 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_ComputingQuotaEvaluator.cxx b/Framework/Core/test/test_ComputingQuotaEvaluator.cxx index 0df58ae7fed08..afd252ec169d4 100644 --- a/Framework/Core/test/test_ComputingQuotaEvaluator.cxx +++ b/Framework/Core/test/test_ComputingQuotaEvaluator.cxx @@ -28,7 +28,7 @@ TEST_CASE("TestComputingQuotaEvaluator") }; ComputingQuotaConsumer dispose2MB = [bs = 2000000](int taskId, - std::array& offers, + std::array& offers, ComputingQuotaStats& stats, std::function accountDisposed) { ComputingQuotaOffer disposed; @@ -51,7 +51,7 @@ TEST_CASE("TestComputingQuotaEvaluator") }; ComputingQuotaConsumer dispose10MB = [bs = 10000000](int taskId, - std::array& offers, + std::array& offers, ComputingQuotaStats& stats, std::function accountDisposed) { ComputingQuotaOffer disposed; diff --git a/Framework/Core/test/test_Concepts.cxx b/Framework/Core/test/test_Concepts.cxx index ea94c4dfffe5a..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); @@ -174,6 +174,7 @@ TEST_CASE("IdentificationConcepts") REQUIRE(is_configurable_axis); REQUIRE(is_process_configurable); + REQUIRE(is_process_configurable); struct : ConfigurableGroup { Configurable c{"", 1, ""}; 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 7d5a3ded88e16..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 @@ -83,7 +88,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -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); } // @@ -133,7 +138,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(4); // Let's create a dummy O2 Message with two headers in the stack: @@ -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 @@ -195,7 +200,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAll(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(4); auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); @@ -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 @@ -276,7 +281,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAll(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(3); auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); @@ -359,7 +364,7 @@ TEST_CASE("DataRelayer") std::vector infos{1}; TimesliceIndex index{1, infos}; ref.registerService(ServiceRegistryHelpers::handleForService(&index)); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); // Only two messages to fill the cache. relayer.setPipelineLength(2); @@ -437,7 +442,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::processWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); // Only two messages to fill the cache. relayer.setPipelineLength(2); @@ -509,7 +514,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::processWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); // Only two messages to fill the cache. relayer.setPipelineLength(3); @@ -568,7 +573,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::processWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); // Only two messages to fill the cache. relayer.setPipelineLength(1); @@ -629,7 +634,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::processWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); // Only two messages to fill the cache. relayer.setPipelineLength(1); @@ -698,7 +703,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(4); DataHeader dh{"CLUSTERS", "TPC", 0}; @@ -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") @@ -752,7 +757,7 @@ TEST_CASE("DataRelayer") ref.registerService(ServiceRegistryHelpers::handleForService(&index)); auto policy = CompletionPolicyHelpers::consumeWhenAny(); - DataRelayer relayer(policy, inputs, index, {registry}); + DataRelayer relayer(policy, inputs, index, {registry}, -1); relayer.setPipelineLength(4); auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); @@ -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_Expressions.cxx b/Framework/Core/test/test_Expressions.cxx index 4c6fc51795ca8..b4a65fb0c7b48 100644 --- a/Framework/Core/test/test_Expressions.cxx +++ b/Framework/Core/test/test_Expressions.cxx @@ -12,6 +12,7 @@ #include "Framework/Configurable.h" #include "Framework/ExpressionHelpers.h" #include "Framework/AnalysisDataModel.h" +#include "../src/ExpressionJSONHelpers.h" #include #include #include @@ -391,3 +392,95 @@ TEST_CASE("TestStringExpressionsParsing") REQUIRE(tree1c->ToString() == tree2c->ToString()); } + +TEST_CASE("TestExpressionSerialization") +{ + Filter f = o2::aod::track::signed1Pt > 0.f && ifnode(nabs(o2::aod::track::eta) < 1.0f, nabs(o2::aod::track::x) > 2.0f, nabs(o2::aod::track::y) > 3.0f); + Projector p = -1.f * nlog(ntan(o2::constants::math::PIQuarter - 0.5f * natan(o2::aod::fwdtrack::tgl))); + Projector p1 = ifnode(o2::aod::track::itsClusterSizes > (uint32_t)0, static_cast(o2::aod::track::ITS), (uint8_t)0x0) | + ifnode(o2::aod::track::tpcNClsFindable > (uint8_t)0, static_cast(o2::aod::track::TPC), (uint8_t)0x0) | + ifnode(o2::aod::track::trdPattern > (uint8_t)0, static_cast(o2::aod::track::TRD), (uint8_t)0x0) | + ifnode((o2::aod::track::tofChi2 >= 0.f) && (o2::aod::track::tofExpMom > 0.f), static_cast(o2::aod::track::TOF), (uint8_t)0x0); + + std::vector projectors; + projectors.emplace_back(std::move(f)); + projectors.emplace_back(std::move(p)); + projectors.emplace_back(std::move(p1)); + + std::stringstream osm; + ExpressionJSONHelpers::write(osm, projectors); + + std::stringstream ism; + ism.str(osm.str()); + auto ps = ExpressionJSONHelpers::read(ism); + + auto s1 = createOperations(projectors[0]); + auto s2 = createOperations(ps[0]); + auto schemaf = std::make_shared(std::vector{o2::aod::track::Eta::asArrowField(), o2::aod::track::Signed1Pt::asArrowField(), o2::aod::track::X::asArrowField(), o2::aod::track::Y::asArrowField()}); + auto t1 = createExpressionTree(s1, schemaf); + auto t2 = createExpressionTree(s2, schemaf); + REQUIRE(t1->ToString() == t2->ToString()); + + auto s12 = createOperations(projectors[1]); + auto s22 = createOperations(ps[1]); + auto schemap = std::make_shared(std::vector{o2::aod::fwdtrack::Tgl::asArrowField()}); + auto t12 = createExpressionTree(s12, schemap); + auto t22 = createExpressionTree(s22, schemap); + REQUIRE(t12->ToString() == t22->ToString()); + + auto s13 = createOperations(projectors[2]); + auto s23 = createOperations(ps[2]); + auto schemap1 = std::make_shared(std::vector{o2::aod::track::ITSClusterSizes::asArrowField(), o2::aod::track::TPCNClsFindable::asArrowField(), + o2::aod::track::TRDPattern::asArrowField(), o2::aod::track::TOFChi2::asArrowField(), + o2::aod::track::TOFExpMom::asArrowField()}); + auto t13 = createExpressionTree(s13, schemap1); + auto t23 = createExpressionTree(s23, schemap1); + REQUIRE(t13->ToString() == t23->ToString()); + + osm.clear(); + osm.str(""); + ArrowJSONHelpers::write(osm, schemaf); + + ism.clear(); + ism.str(osm.str()); + auto newSchemaf = ArrowJSONHelpers::read(ism); + REQUIRE(schemaf->ToString() == newSchemaf->ToString()); + + osm.clear(); + osm.str(""); + ArrowJSONHelpers::write(osm, schemap); + + ism.clear(); + ism.str(osm.str()); + auto newSchemap = ArrowJSONHelpers::read(ism); + REQUIRE(schemap->ToString() == newSchemap->ToString()); + + osm.clear(); + osm.str(""); + ArrowJSONHelpers::write(osm, schemap1); + + ism.clear(); + ism.str(osm.str()); + auto newSchemap1 = ArrowJSONHelpers::read(ism); + REQUIRE(schemap1->ToString() == newSchemap1->ToString()); + + osm.clear(); + osm.str(""); + auto realisticSchema = std::make_shared(o2::soa::createFieldsFromColumns(o2::aod::MetadataTrait>::metadata::persistent_columns_t{})); + ArrowJSONHelpers::write(osm, realisticSchema); + + ism.clear(); + ism.str(osm.str()); + auto restoredSchema = ArrowJSONHelpers::read(ism); + REQUIRE(realisticSchema->ToString() == restoredSchema->ToString()); + + osm.clear(); + osm.str(""); + auto realisticSchema1 = std::make_shared(o2::soa::createFieldsFromColumns(o2::aod::MetadataTrait>::metadata::persistent_columns_t{})); + ArrowJSONHelpers::write(osm, realisticSchema1); + + ism.clear(); + ism.str(osm.str()); + auto restoredSchema1 = ArrowJSONHelpers::read(ism); + REQUIRE(realisticSchema1->ToString() == restoredSchema1->ToString()); +} 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 new file mode 100644 index 0000000000000..0263158ee0f9b --- /dev/null +++ b/Framework/Core/test/test_ForwardInputs.cxx @@ -0,0 +1,794 @@ +// Copyright 2019-2025 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 "Headers/DataHeader.h" +#include "Framework/DataProcessingHeader.h" +#include "Framework/DataProcessingHelpers.h" +#include "Framework/SourceInfoHeader.h" +#include "Framework/DomainInfoHeader.h" +#include "Framework/Signpost.h" +#include "Framework/DataModelViews.h" +#include "Framework/FairMQDeviceProxy.h" +#include "Headers/Stack.h" +#include "MemoryResources/MemoryResources.h" +#include +#include +#include + +O2_DECLARE_DYNAMIC_LOG(forwarding); +using namespace o2::framework; + +TEST_CASE("ForwardInputsEmpty") +{ + o2::header::DataHeader dh; + dh.dataDescription = "CLUSTERS"; + dh.dataOrigin = "TPC"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq"); + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + + std::vector> currentSetOfInputs; + + auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); + REQUIRE(result.empty()); +} + +TEST_CASE("ForwardInputsSingleMessageSingleRoute") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); // One route + REQUIRE(result[0].Size() == 2); // Two messages for that route +} + +TEST_CASE("ForwardInputsSingleMessageSingleRouteNoConsume") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool copyByDefault = false; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); + REQUIRE(result[0].Size() == 0); // Because there is a nullptr, we do not forward this as it was already consumed. +} + +TEST_CASE("ForwardInputsSingleMessageSingleRouteAtEOS") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + o2::framework::SourceInfoHeader sih{}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); // One route + REQUIRE(result[0].Size() == 0); // FIXME: this is an actual error. It should be 2. However it cannot really happen. + // Correct behavior below: + // REQUIRE(result[0].Size() == 2); + // REQUIRE(o2::header::get(result[0].At(0)->GetData()) == nullptr); +} + +TEST_CASE("ForwardInputsSingleMessageSingleRouteWithOldestPossible") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + o2::framework::DomainInfoHeader dih{}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); // One route + REQUIRE(result[0].Size() == 0); // FIXME: this is actually wrong + // FIXME: actually correct behavior below + // REQUIRE(result[0].Size() == 2); // Two messages + // REQUIRE(o2::header::get(result[0].At(0)->GetData()) == nullptr); // it should not have the end of stream +} + +TEST_CASE("ForwardInputsSingleMessageMultipleRoutes") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding2", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 2); // Two routes + REQUIRE(result[0].Size() == 2); // Two messages per route + REQUIRE(result[1].Size() == 0); // Only the first DPL matched channel matters +} + +TEST_CASE("ForwardInputsSingleMessageMultipleRoutesExternals") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("external"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "external", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding2", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 2); // Two routes + REQUIRE(result[0].Size() == 2); // With external matching channels, we need to copy and then forward + REQUIRE(result[1].Size() == 2); // +} + +TEST_CASE("ForwardInputsMultiMessageMultipleRoutes") +{ + o2::header::DataHeader dh1; + dh1.dataOrigin = "TST"; + dh1.dataDescription = "A"; + dh1.subSpecification = 0; + dh1.splitPayloadIndex = 0; + dh1.splitPayloadParts = 1; + + o2::header::DataHeader dh2; + dh2.dataOrigin = "TST"; + dh2.dataDescription = "B"; + dh2.subSpecification = 0; + dh2.splitPayloadIndex = 0; + dh2.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding2", ConcreteDataMatcher{"TST", "B", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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}); + 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}); + 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); + + auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); + REQUIRE(result.size() == 2); // Two routes + REQUIRE(result[0].Size() == 2); // + REQUIRE(result[1].Size() == 2); // +} + +TEST_CASE("ForwardInputsSingleMessageMultipleRoutesOnlyOneMatches") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 0; + dh.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "B", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 2); // Two routes + REQUIRE(result[0].Size() == 0); // Two messages per route + REQUIRE(result[1].Size() == 2); // Two messages per route +} + +TEST_CASE("ForwardInputsSplitPayload") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 2; + dh.splitPayloadParts = 2; + + o2::header::DataHeader dh2; + dh2.dataOrigin = "TST"; + dh2.dataDescription = "B"; + dh2.subSpecification = 0; + dh2.splitPayloadIndex = 0; + dh2.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "B", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + std::vector> currentSetOfInputs; + std::vector messageSet; + + 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 header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); + std::vector> messages; + messages.push_back(std::move(header)); + messages.push_back(std::move(payload1)); + messages.push_back(std::move(payload2)); + auto fillMessages = [&messages](size_t t) -> fair::mq::MessagePtr { + return std::move(messages[t]); + }; + for (size_t i = 0; i < 3; ++i) { + messageSet.emplace_back(fillMessages(i)); + } + auto header2 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh2, dph}); + messageSet.emplace_back(std::move(header2)); + messageSet.emplace_back(transport->CreateMessage()); + + REQUIRE((messageSet | count_parts{}) == 2); + currentSetOfInputs.emplace_back(std::move(messageSet)); + + auto result = o2::framework::DataProcessingHelpers::routeForwardedMessageSet(proxy, currentSetOfInputs, copyByDefault, consume); + REQUIRE(result.size() == 2); // Two routes + CHECK(result[0].Size() == 2); // No messages on this route + CHECK(result[1].Size() == 3); +} + +TEST_CASE("ForwardInputsSplitPayloadNoMessageSet") +{ + o2::header::DataHeader dh; + dh.dataOrigin = "TST"; + dh.dataDescription = "A"; + dh.subSpecification = 0; + dh.splitPayloadIndex = 2; + dh.splitPayloadParts = 2; + + o2::header::DataHeader dh2; + dh2.dataOrigin = "TST"; + dh2.dataDescription = "B"; + dh2.subSpecification = 0; + dh2.splitPayloadIndex = 0; + dh2.splitPayloadParts = 1; + + o2::framework::DataProcessingHeader dph{0, 1}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B"), + fair::mq::Channel("from_A_to_C"), + }; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "B", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }, + ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_C", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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 header = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh, dph}); + std::vector> messages; + messages.push_back(std::move(header)); + messages.push_back(std::move(payload1)); + messages.push_back(std::move(payload2)); + auto header2 = o2::pmr::getMessage(o2::header::Stack{channelAlloc, dh2, dph}); + messages.push_back(std::move(header2)); + messages.push_back(transport->CreateMessage()); + + std::vector result(2); + auto span = std::span(messages); + o2::framework::DataProcessingHelpers::routeForwardedMessages(proxy, span, result, copyByDefault, consume); + REQUIRE(result.size() == 2); // Two routes + CHECK(result[0].Size() == 2); // No messages on this route + CHECK(result[1].Size() == 3); +} + +TEST_CASE("ForwardInputEOSSingleRoute") +{ + o2::framework::SourceInfoHeader sih{}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); // One route + REQUIRE(result[0].Size() == 0); // Oldest possible timeframe should not be forwarded +} + +TEST_CASE("ForwardInputOldestPossibleSingleRoute") +{ + o2::framework::DomainInfoHeader dih{}; + + std::vector channels{ + fair::mq::Channel("from_A_to_B")}; + + bool consume = true; + bool copyByDefault = true; + FairMQDeviceProxy proxy; + std::vector routes{ForwardRoute{ + .timeslice = 0, + .maxTimeslices = 1, + .matcher = {"binding", ConcreteDataMatcher{"TST", "A", 0}}, + .channel = "from_A_to_B", + .policy = nullptr, + }}; + + auto findChannelByName = [&channels](std::string const& channelName) -> fair::mq::Channel& { + for (auto& channel : channels) { + if (channel.GetName() == channelName) { + return channel; + } + } + throw std::runtime_error("Channel not found"); + }; + + proxy.bind({}, {}, routes, findChannelByName, nullptr); + + 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.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); + REQUIRE(result.size() == 1); // One route + REQUIRE(result[0].Size() == 0); // Oldest possible timeframe should not be forwarded +} diff --git a/Framework/Core/test/test_FrameworkDataFlowToO2Control.cxx b/Framework/Core/test/test_FrameworkDataFlowToO2Control.cxx index 32dd0b5922a4f..abf6d64a7ca1e 100644 --- a/Framework/Core/test/test_FrameworkDataFlowToO2Control.cxx +++ b/Framework/Core/test/test_FrameworkDataFlowToO2Control.cxx @@ -138,7 +138,7 @@ const std::vector expectedTasks{ defaults: log_task_stdout: none log_task_stderr: none - exit_transition_timeout: 25 + exit_transition_timeout: 40 data_processing_timeout: 20 _module_cmdline: >- source /etc/profile.d/modules.sh && MODULEPATH={{ modulepath }} module load O2 QualityControl Control-OCCPlugin && @@ -236,7 +236,7 @@ const std::vector expectedTasks{ defaults: log_task_stdout: none log_task_stderr: none - exit_transition_timeout: 25 + exit_transition_timeout: 40 data_processing_timeout: 20 _module_cmdline: >- source /etc/profile.d/modules.sh && MODULEPATH={{ modulepath }} module load O2 QualityControl Control-OCCPlugin && @@ -336,7 +336,7 @@ const std::vector expectedTasks{ defaults: log_task_stdout: none log_task_stderr: none - exit_transition_timeout: 25 + exit_transition_timeout: 40 data_processing_timeout: 20 _module_cmdline: >- source /etc/profile.d/modules.sh && MODULEPATH={{ modulepath }} module load O2 QualityControl Control-OCCPlugin && @@ -436,7 +436,7 @@ const std::vector expectedTasks{ defaults: log_task_stdout: none log_task_stderr: none - exit_transition_timeout: 25 + exit_transition_timeout: 40 data_processing_timeout: 20 _module_cmdline: >- source /etc/profile.d/modules.sh && MODULEPATH={{ modulepath }} module load O2 QualityControl Control-OCCPlugin && diff --git a/Framework/Core/test/test_GroupSlicer.cxx b/Framework/Core/test/test_GroupSlicer.cxx index 2f21d7dd17975..71360f736c3fb 100644 --- a/Framework/Core/test/test_GroupSlicer.cxx +++ b/Framework/Core/test/test_GroupSlicer.cxx @@ -117,7 +117,8 @@ TEST_CASE("GroupSlicerOneAssociated") REQUIRE(t.size() == 10 * 20); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -191,9 +192,9 @@ TEST_CASE("GroupSlicerSeveralAssociated") auto tt = std::make_tuple(tx, ty, tz, tu); auto key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), key}, - {soa::getLabelFromType(), key}, - {soa::getLabelFromType(), key}}); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}, + {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}, + {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, {trkTableX}); s = slices.updateCacheEntry(1, {trkTableY}); s = slices.updateCacheEntry(2, {trkTableZ}); @@ -256,7 +257,8 @@ TEST_CASE("GroupSlicerMismatchedGroups") REQUIRE(t.size() == 10 * (20 - 5)); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -312,7 +314,8 @@ TEST_CASE("GroupSlicerMismatchedUnassignedGroups") REQUIRE(t.size() == (30 + 10 * (20 - 5))); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -362,7 +365,8 @@ TEST_CASE("GroupSlicerMismatchedFilteredGroups") REQUIRE(t.size() == 10 * (20 - 4)); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -423,7 +427,8 @@ TEST_CASE("GroupSlicerMismatchedUnsortedFilteredGroups") REQUIRE(t.size() == 10 * (20 - 4)); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({}, {{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({}, {{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntryUnsorted(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -547,8 +552,9 @@ TEST_CASE("GroupSlicerMismatchedUnsortedFilteredGroupsWithSelfIndex") } FilteredParts fp{{partsTable}, rows}; auto associatedTuple = std::make_tuple(fp, t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}, - {soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}, + {soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s0 = slices.updateCacheEntry(0, partsTable); auto s1 = slices.updateCacheEntry(1, thingsTable); o2::framework::GroupSlicer g(e, associatedTuple, slices); @@ -607,7 +613,8 @@ TEST_CASE("EmptySliceables") REQUIRE(t.size() == 0); auto tt = std::make_tuple(t); - ArrowTableSlicingCache slices({{soa::getLabelFromType(), "fIndex" + o2::framework::cutString(soa::getLabelFromType())}}); + std::string key = "fIndex" + o2::framework::cutString(soa::getLabelFromType()); + ArrowTableSlicingCache slices({{soa::getLabelFromType(), soa::getMatcherFromTypeForKey(key), key}}); auto s = slices.updateCacheEntry(0, trkTable); o2::framework::GroupSlicer g(e, tt, slices); @@ -679,7 +686,7 @@ TEST_CASE("ArrowDirectSlicing") std::vector slices; std::vector offsts; - auto bk = Entry(soa::getLabelFromType(), "fID"); + auto bk = Entry(soa::getLabelFromType(), soa::getMatcherFromTypeForKey("fID"), "fID"); ArrowTableSlicingCache cache({bk}); auto s = cache.updateCacheEntry(0, {evtTable}); auto lcache = cache.getCacheFor(bk); @@ -737,7 +744,7 @@ TEST_CASE("TestSlicingException") } auto evtTable = builderE.finalize(); - auto bk = Entry(soa::getLabelFromType(), "fID"); + auto bk = Entry(soa::getLabelFromType(), soa::getMatcherFromTypeForKey("fID"), "fID"); ArrowTableSlicingCache cache({bk}); try { diff --git a/Framework/Core/test/test_IndexBuilder.cxx b/Framework/Core/test/test_IndexBuilder.cxx index ea9f715f20c8a..2a273db4333fc 100644 --- a/Framework/Core/test/test_IndexBuilder.cxx +++ b/Framework/Core/test/test_IndexBuilder.cxx @@ -10,7 +10,7 @@ // or submit itself to any jurisdiction. #include "Framework/AnalysisDataModel.h" -#include "Framework/AnalysisTask.h" +#include "../src/IndexJSONHelpers.h" #include using namespace o2::framework; @@ -102,10 +102,13 @@ TEST_CASE("TestIndexBuilder") auto t4 = b4.finalize(); Categorys st4{t4}; - using m1 = MetadataTrait>::metadata; - auto t5 = IndexBuilder::indexBuilder("test1a", {t1, t2, t3, t4}, typename IDXs::persistent_columns_t{}); + auto map = getIndexMapping>::metadata>(); + auto schema1 = o2::aod::MetadataTrait>::metadata::getSchema(); + std::vector builders1; + 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()); @@ -113,10 +116,12 @@ TEST_CASE("TestIndexBuilder") REQUIRE(row.category().pointId() == row.pointId()); } - using m2 = MetadataTrait>::metadata; - auto t6 = IndexBuilder::indexBuilder("test3", {t2, t1, t3, t4}, typename IDX2s::persistent_columns_t{}); + map = getIndexMapping>::metadata>(); + auto schema2 = o2::aod::MetadataTrait>::metadata::getSchema(); + 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); @@ -212,10 +217,12 @@ TEST_CASE("AdvancedIndexTables") {14, 34}, {8, 31, 42, 46, 58}}}; - using m3 = MetadataTrait>::metadata; - auto t3 = IndexBuilder::indexBuilder("test4", {t1, t2, tc}, typename IDX3s::persistent_columns_t{}); + auto map = getIndexMapping>::metadata>(); + auto schema3 = o2::aod::MetadataTrait>::metadata::getSchema(); + 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) { @@ -235,3 +242,38 @@ TEST_CASE("AdvancedIndexTables") ++count; } } + +TEST_CASE("IndexRecordsSerialization") +{ + auto map = getIndexMapping>::metadata>(); + + std::stringstream osm; + IndexJSONHelpers::write(osm, map); + + std::stringstream ism; + ism.str(osm.str()); + auto rmap = IndexJSONHelpers::read(ism); + REQUIRE(map == rmap); + + map = getIndexMapping>::metadata>(); + + osm.clear(); + osm.str(""); + IndexJSONHelpers::write(osm, map); + + ism.clear(); + ism.str(osm.str()); + rmap = IndexJSONHelpers::read(ism); + REQUIRE(map == rmap); + + map = getIndexMapping>::metadata>(); + + osm.clear(); + osm.str(""); + IndexJSONHelpers::write(osm, map); + + ism.clear(); + ism.str(osm.str()); + rmap = IndexJSONHelpers::read(ism); + REQUIRE(map == rmap); +} 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 new file mode 100644 index 0000000000000..caa9a60323306 --- /dev/null +++ b/Framework/Core/test/test_MessageSet.cxx @@ -0,0 +1,379 @@ +// Copyright 2019-2025 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/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") +{ + 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); + std::vector ptrs; + ptrs.emplace_back(std::move(header)); + ptrs.emplace_back(std::move(msg2)); + for (size_t i = 0; i < 2; ++i) { + messages.emplace_back(std::move(ptrs[i])); + } + + 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") +{ + std::vector ptrs; + 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(header)); + ptrs.emplace_back(std::move(msg2)); + std::vector messages; + for (size_t i = 0; i < 2; ++i) { + messages.emplace_back(std::move(ptrs[i])); + } + + 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") +{ + std::vector ptrs; + 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(header)); + ptrs.emplace_back(std::move(msg2)); + ptrs.emplace_back(std::move(msg3)); + 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::unique_ptr msg(nullptr); + std::unique_ptr msg2(nullptr); + 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") +{ + 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_ResourcesMonitoringHelpers.cxx b/Framework/Core/test/test_ResourcesMonitoringHelpers.cxx new file mode 100644 index 0000000000000..e31ccea0f5a35 --- /dev/null +++ b/Framework/Core/test/test_ResourcesMonitoringHelpers.cxx @@ -0,0 +1,272 @@ +// Copyright 2019-2025 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 "../src/ResourcesMonitoringHelper.h" +#include "Framework/DeviceMetricsInfo.h" +#include "Framework/DeviceMetricsHelper.h" + +#include +#include +#include +#include + +TEST_CASE("StreamMetrics") +{ + using namespace o2::framework; + std::vector specs{ + DeviceSpec{ + .name = "someDevice", + .id = "someDevice", + .inputChannels = {}, + .outputChannels = {}, + .arguments = {}, + .options = {}, + .services = {}, + .algorithm = AlgorithmSpec{}, + .inputs = {}, + .outputs = {}, + .forwards = {}, + .rank = 0, + .nSlots = 0, + .inputTimesliceId = 0, + .maxInputTimeslices = 0, + .completionPolicy = CompletionPolicy{}, + .dispatchPolicy = DispatchPolicy{}, + .callbacksPolicy = CallbacksPolicy{}, + .sendingPolicy = SendingPolicy{}, + .resourcePolicy = ResourcePolicy{}, + .resource = {}, + .resourceMonitoringInterval = 10, + .labels = {}, + .metadata = {}}, + DeviceSpec{ + .name = "anotherDevice", + .id = "anotherDevice", + .inputChannels = {}, + .outputChannels = {}, + .arguments = {}, + .options = {}, + .services = {}, + .algorithm = AlgorithmSpec{}, + .inputs = {}, + .outputs = {}, + .forwards = {}, + .rank = 0, + .nSlots = 0, + .inputTimesliceId = 0, + .maxInputTimeslices = 0, + .completionPolicy = CompletionPolicy{}, + .dispatchPolicy = DispatchPolicy{}, + .callbacksPolicy = CallbacksPolicy{}, + .sendingPolicy = SendingPolicy{}, + .resourcePolicy = ResourcePolicy{}, + .resource = {}, + .resourceMonitoringInterval = 10, + .labels = {}, + .metadata = {}}, + + }; + + // This is the device metrics + std::vector metrics; + metrics.resize(2); + { + DeviceMetricsInfo& info = metrics[0]; + auto bkey = DeviceMetricsHelper::createNumericMetric(info, "bkey"); + REQUIRE(info.metricLabels.size() == 1); + REQUIRE(info.metricPrefixes.size() == 1); + auto akey = DeviceMetricsHelper::createNumericMetric(info, "akey"); + REQUIRE(info.metricLabels.size() == 2); + REQUIRE(info.metricPrefixes.size() == 2); + auto ckey = DeviceMetricsHelper::createNumericMetric(info, "ckey"); + REQUIRE(info.metricLabels.size() == 3); + REQUIRE(info.metricPrefixes.size() == 3); + REQUIRE(DeviceMetricsHelper::metricIdxByName("akey", info) == 1); + REQUIRE(DeviceMetricsHelper::metricIdxByName("bkey", info) == 0); + REQUIRE(DeviceMetricsHelper::metricIdxByName("ckey", info) == 2); + REQUIRE(info.changed.size() == 3); + REQUIRE(info.changed.at(0) == false); + size_t t = 1000; + ckey(info, 0, t++); + ckey(info, 1, t++); + ckey(info, 2, t++); + ckey(info, 3, t++); + ckey(info, 4, t++); + ckey(info, 5, t++); + } + // Metrics for the second device + { + DeviceMetricsInfo& info = metrics[1]; + auto bkey = DeviceMetricsHelper::createNumericMetric(info, "bkey"); + REQUIRE(info.metricLabels.size() == 1); + REQUIRE(info.metricPrefixes.size() == 1); + auto akey = DeviceMetricsHelper::createNumericMetric(info, "akey"); + REQUIRE(info.metricLabels.size() == 2); + REQUIRE(info.metricPrefixes.size() == 2); + auto ckey = DeviceMetricsHelper::createNumericMetric(info, "ckey"); + REQUIRE(info.metricLabels.size() == 3); + REQUIRE(info.metricPrefixes.size() == 3); + REQUIRE(DeviceMetricsHelper::metricIdxByName("akey", info) == 1); + REQUIRE(DeviceMetricsHelper::metricIdxByName("bkey", info) == 0); + REQUIRE(DeviceMetricsHelper::metricIdxByName("ckey", info) == 2); + REQUIRE(info.changed.size() == 3); + REQUIRE(info.changed.at(0) == false); + size_t t = 1000; + bkey(info, 0, t++); + bkey(info, 1, t++); + bkey(info, 2, t++); + bkey(info, 3, t++); + bkey(info, 4, t++); + bkey(info, 5, t++); + } + + // This is the driver metrics + DeviceMetricsInfo driverMetrics; + auto dbkey = DeviceMetricsHelper::createNumericMetric(driverMetrics, "bkey"); + REQUIRE(driverMetrics.metricLabels.size() == 1); + REQUIRE(driverMetrics.metricPrefixes.size() == 1); + auto dakey = DeviceMetricsHelper::createNumericMetric(driverMetrics, "akey"); + REQUIRE(driverMetrics.metricLabels.size() == 2); + REQUIRE(driverMetrics.metricPrefixes.size() == 2); + auto dckey = DeviceMetricsHelper::createNumericMetric(driverMetrics, "ckey"); + REQUIRE(driverMetrics.metricLabels.size() == 3); + REQUIRE(driverMetrics.metricPrefixes.size() == 3); + REQUIRE(DeviceMetricsHelper::metricIdxByName("akey", driverMetrics) == 1); + REQUIRE(DeviceMetricsHelper::metricIdxByName("bkey", driverMetrics) == 0); + REQUIRE(DeviceMetricsHelper::metricIdxByName("ckey", driverMetrics) == 2); + REQUIRE(driverMetrics.changed.size() == 3); + REQUIRE(driverMetrics.changed.at(0) == false); + size_t t = 2000; + dbkey(driverMetrics, 0, t++); + dbkey(driverMetrics, 1, t++); + dbkey(driverMetrics, 2, t++); + dbkey(driverMetrics, 3, t++); + dbkey(driverMetrics, 4, t++); + dbkey(driverMetrics, 5, t++); + + dbkey(driverMetrics, 0, t++); + dbkey(driverMetrics, 1, t++); + + dckey(driverMetrics, 0, t++); + + std::stringstream streamer; + std::vector performanceMetrics{ + std::regex("bkey"), + std::regex("ckey"), + }; + + ResourcesMonitoringHelper::dumpMetricsToJSON(metrics, + driverMetrics, specs, performanceMetrics, + streamer); + std::string streamed = streamer.str(); + std::string expected = R"JSON({ + "someDevice": { + "ckey": [ + { + "timestamp": "1000", + "value": "0" + }, + { + "timestamp": "1001", + "value": "1" + }, + { + "timestamp": "1002", + "value": "2" + }, + { + "timestamp": "1003", + "value": "3" + }, + { + "timestamp": "1004", + "value": "4" + }, + { + "timestamp": "1005", + "value": "5" + } + ] + }, + "anotherDevice": { + "bkey": [ + { + "timestamp": "1000", + "value": "0" + }, + { + "timestamp": "1001", + "value": "1" + }, + { + "timestamp": "1002", + "value": "2" + }, + { + "timestamp": "1003", + "value": "3" + }, + { + "timestamp": "1004", + "value": "4" + }, + { + "timestamp": "1005", + "value": "5" + } + ] + }, + "driver": { + "bkey": [ + { + "timestamp": "2000", + "value": "0" + }, + { + "timestamp": "2001", + "value": "1" + }, + { + "timestamp": "2002", + "value": "2" + }, + { + "timestamp": "2003", + "value": "3" + }, + { + "timestamp": "2004", + "value": "4" + }, + { + "timestamp": "2005", + "value": "5" + }, + { + "timestamp": "2006", + "value": "0" + }, + { + "timestamp": "2007", + "value": "1" + } + ], + "ckey": [ + { + "timestamp": "2008", + "value": "0" + } + ] + } +} +)JSON"; + REQUIRE(std::regex_replace(streamed, std::regex(R"(\s+)"), "") == std::regex_replace(expected, std::regex(R"(\s+)"), "")); +} diff --git a/Framework/Core/test/test_TypeToTaskName.cxx b/Framework/Core/test/test_TypeToTaskName.cxx new file mode 100644 index 0000000000000..cd5a359db0446 --- /dev/null +++ b/Framework/Core/test/test_TypeToTaskName.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 +#include "Framework/AnalysisTask.h" +#include + +using namespace o2::framework; + +TEST_CASE("TypeIdHelpers_BasicConversion") +{ + // Basic CamelCase to snake-case conversion +// REQUIRE((type_to_task_name(std::string_view("SimpleTask")) == "simple-task")); +// REQUIRE((type_to_task_name(std::string_view("MyTask")) == "my-task")); +// REQUIRE((type_to_task_name(std::string_view("Task")) == "task")); +} + +TEST_CASE("TypeIdHelpers_AbbreviationConsolidation") +{ +// Test ALICE detector abbreviations +// REQUIRE(type_to_task_name(std::string_view("ITSQA")) == "its-qa"); +// REQUIRE(type_to_task_name(std::string_view("TPCQCTask")) == "tpc-qc-task"); +// REQUIRE(type_to_task_name(std::string_view("EMCALQATask")) == "emcal-qa-task"); +// REQUIRE(type_to_task_name(std::string_view("HMPIDTask")) == "hmpid-task"); +// REQUIRE(type_to_task_name(std::string_view("ITSTPCTask")) == "its-tpc-task"); +// REQUIRE(type_to_task_name(std::string_view("QCFV0Task")) == "qc-fv0-task"); +} + +//TEST_CASE("TypeIdHelpers_QualityControlAbbreviations") +//{ +// // Test quality control abbreviations +// REQUIRE(type_to_task_name(std::string_view("QATask")) == "qa-task"); +// REQUIRE(type_to_task_name(std::string_view("QCTask")) == "qc-task"); +// REQUIRE(type_to_task_name(std::string_view("QCDAnalysis")) == "qcd-analysis"); +//} + +//TEST_CASE("TypeIdHelpers_ComplexNames") +//{ +// Test complex combinations +// REQUIRE(type_to_task_name(std::string_view("ITSQAAnalysisTask")) == "its-qa-analysis-task"); +// REQUIRE(type_to_task_name(std::string_view("TPCEMCQCTask")) == "tpc-emc-qc-task"); +// REQUIRE(type_to_task_name(std::string_view("MyITSTask")) == "my-its-task"); +//} + +//TEST_CASE("TypeIdHelpers_EdgeCases") +//{ +// // Single character +// REQUIRE(type_to_task_name(std::string_view("A")) == "a"); +// +// // All uppercase. BC is Bunch Crossing! +// // +// REQUIRE(type_to_task_name(std::string_view("ABC")) == "a-bc"); +// REQUIRE(type_to_task_name(std::string_view("BC")) == "bc"); +// +// // Mixed with numbers (numbers are not uppercase, so no hyphens before them) +// REQUIRE(type_to_task_name(std::string_view("Task123")) == "task123"); +//} 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/CheckTypes.h b/Framework/Foundation/include/Framework/CheckTypes.h index f0c74c54f9790..95e99a67ac0cf 100644 --- a/Framework/Foundation/include/Framework/CheckTypes.h +++ b/Framework/Foundation/include/Framework/CheckTypes.h @@ -18,13 +18,15 @@ namespace o2::framework { -/// Helper to understand if a given type is complete (declared fully) or not (forward declared). -/// See also: https://devblogs.microsoft.com/oldnewthing/20190710-00/?p=102678 -template -constexpr bool is_type_complete_v = false; +template +concept TypeComplete = requires(T) { + { + sizeof(T) + }; +}; template -constexpr bool is_type_complete_v> = true; +constexpr bool is_type_complete_v = TypeComplete; /// Helper which will invoke @a onDefined if the type T is actually available /// or @a onUndefined if the type T is a forward declaration. @@ -39,31 +41,12 @@ void call_if_defined_full(TDefined&& onDefined, TUndefined&& onUndefined) } } -/// Helper which will invoke @a onDefined if the type T is actually available -/// or @a onUndefined if the type T is a forward declaration. -/// Can be used to check for existence or not of a given type. -template -T call_if_defined_full_forward(TDefined&& onDefined, TUndefined&& onUndefined) -{ - if constexpr (is_type_complete_v) { - return std::move(onDefined(static_cast(nullptr))); - } else { - return onUndefined(); - } -} - template void call_if_defined(TDefined&& onDefined) { call_if_defined_full(onDefined, []() -> void {}); } -template -T call_if_defined_forward(TDefined&& onDefined) -{ - return std::move(call_if_defined_full_forward(onDefined, []() -> T&& { O2_BUILTIN_UNREACHABLE(); })); -} - } // namespace o2::framework #endif // O2_FRAMEWORK_CHECKTYPES_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 0ea0a1f5ec75b..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 @@ -246,6 +247,7 @@ inline _o2_signpost_id_t _o2_signpost_id_make_with_pointer(_o2_log_t* log, void #include #include "Framework/RuntimeError.h" #include "Framework/BacktraceHelpers.h" +void _o2_signpost_event_emit_v(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, va_list args); void _o2_signpost_interval_end_v(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, va_list args); // returns true if the push was successful, false if the stack was full @@ -358,11 +360,8 @@ void* _o2_log_create(char const* name, int defaultStacktrace) // This will look at the slot in the log associated to the ID. // If the slot is empty, it will return the id and increment the indentation level. -void _o2_signpost_event_emit(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, ...) +void _o2_signpost_event_emit_v(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, va_list args) { - va_list args; - va_start(args, format); - // Find the index of the activity int leading = 0; @@ -386,7 +385,6 @@ void _o2_signpost_event_emit(_o2_log_t* log, _o2_signpost_id_t id, char const* n char prebuffer[4096]; int s = snprintf(prebuffer, 4096, "id%.16" PRIx64 ":%-16s*>%*c", id.value, name, leading, ' '); vsnprintf(prebuffer + s, 4096 - s, format, args); - va_end(args); O2_LOG_MACRO("%s", prebuffer); if (log->stacktrace > 1) { void* traces[o2::framework::BacktraceHelpers::MAX_BACKTRACE_SIZE]; @@ -396,6 +394,15 @@ void _o2_signpost_event_emit(_o2_log_t* log, _o2_signpost_id_t id, char const* n } } +// We separate this so that we can still emit the end signpost when the log is not enabled. +void _o2_signpost_event_emit(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, ...) +{ + va_list args; + va_start(args, format); + _o2_signpost_event_emit_v(log, id, name, format, args); + va_end(args); +} + // This will look at the slot in the log associated to the ID. // If the slot is empty, it will return the id and increment the indentation level. void _o2_signpost_interval_begin(_o2_log_t* log, _o2_signpost_id_t id, char const* name, char const* const format, ...) @@ -434,7 +441,7 @@ void _o2_signpost_interval_end_v(_o2_log_t* log, _o2_signpost_id_t id, char cons // We should not make this an error because one could have enabled the log after the interval // was started. if (i == log->ids.size()) { - _o2_signpost_event_emit(log, id, name, format, args); + _o2_signpost_event_emit_v(log, id, name, format, args); return; } // i is the slot index @@ -605,6 +612,16 @@ void o2_debug_log_set_stacktrace(_o2_log_t* log, int stacktrace) } else if (O2_BUILTIN_UNLIKELY(private_o2_log_##log->stacktrace)) { \ _o2_signpost_interval_end(private_o2_log_##log, id, name, remove_engineering_type(format).data(), ##__VA_ARGS__); \ } +// Print out a message at error level in any case even if the signpost is not enable. +// If it is enabled, behaves like O2_SIGNPOST_END. +#define O2_SIGNPOST_END_WITH_ERROR(log, id, name, format, ...) \ + if (O2_BUILTIN_UNLIKELY(O2_SIGNPOST_ENABLED_MAC(log))) { \ + O2_SIGNPOST_END_MAC(log, id, name, format, ##__VA_ARGS__); \ + } else if (O2_BUILTIN_UNLIKELY(private_o2_log_##log->stacktrace)) { \ + _o2_signpost_interval_end(private_o2_log_##log, id, name, remove_engineering_type(format).data(), ##__VA_ARGS__); \ + } else { \ + O2_LOG_MACRO_RAW(error, remove_engineering_type(format).data(), ##__VA_ARGS__); \ + } #else // This is the release implementation, it does nothing. #define O2_DECLARE_DYNAMIC_LOG(x) #define O2_DECLARE_DYNAMIC_STACKTRACE_LOG(x) diff --git a/Framework/Foundation/include/Framework/StructToTuple.h b/Framework/Foundation/include/Framework/StructToTuple.h index 5748329f6a50d..1c7aa62260bd3 100644 --- a/Framework/Foundation/include/Framework/StructToTuple.h +++ b/Framework/Foundation/include/Framework/StructToTuple.h @@ -174,9 +174,9 @@ consteval int nested_brace_constructible_size() return brace_constructible_size() - nesting; } -template () / 10> +template () / 10, typename L> requires(D == 9) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -194,9 +194,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 8) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -214,9 +214,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 7) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -234,9 +234,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 6) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -254,9 +254,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 5) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -274,9 +274,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 4) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -294,9 +294,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 3) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -314,9 +314,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 2) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -334,9 +334,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 1) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -354,9 +354,9 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } -template () / 10> +template () / 10, typename L> requires(D == 0) -auto homogeneous_apply_refs(L l, T&& object) +constexpr auto homogeneous_apply_refs(L l, T&& object) { constexpr int numElements = nested_brace_constructible_size(); // clang-format off @@ -373,6 +373,12 @@ auto homogeneous_apply_refs(L l, T&& object) // clang-format on } +template +constexpr auto homogeneous_apply_refs_sized(L l, T&& object) +{ + return homogeneous_apply_refs(l, object); +} + } // namespace o2::framework #endif // O2_FRAMEWORK_STRUCTTOTUPLE_H_ diff --git a/Framework/Foundation/include/Framework/TypeIdHelpers.h b/Framework/Foundation/include/Framework/TypeIdHelpers.h index 5eaac2151b909..1dc2464b40ec8 100644 --- a/Framework/Foundation/include/Framework/TypeIdHelpers.h +++ b/Framework/Foundation/include/Framework/TypeIdHelpers.h @@ -13,7 +13,6 @@ #define O2_FRAMEWORK_TYPEIDHELPERS_H_ #include -#include #if __cplusplus >= 202002L #include #endif @@ -82,22 +81,6 @@ struct TypeIdHelpers { } }; -/// Convert a CamelCase task struct name to snake-case task name -inline static std::string type_to_task_name(std::string_view& camelCase) -{ - std::ostringstream str; - str << static_cast(std::tolower(camelCase[0])); - - for (auto it = camelCase.begin() + 1; it != camelCase.end(); ++it) { - if (std::isupper(*it) && *(it - 1) != '-') { - str << "-"; - } - str << static_cast(std::tolower(*it)); - } - - return str.str(); -} - } // namespace o2::framework #endif // O2_FRAMEWORK_TYPEIDHELPERS_H_ diff --git a/Framework/Foundation/src/RuntimeError.cxx b/Framework/Foundation/src/RuntimeError.cxx index 6f31fb40b86f5..7d31389a9641c 100644 --- a/Framework/Foundation/src/RuntimeError.cxx +++ b/Framework/Foundation/src/RuntimeError.cxx @@ -60,8 +60,11 @@ RuntimeError& error_from_ref(RuntimeErrorRef ref) RuntimeErrorRef runtime_error_f(const char* format, ...) { int i = 0; - bool expected = false; - while (gErrorBooking[i].compare_exchange_strong(expected, true) == false) { + while (true) { + bool expected = false; + if (gErrorBooking[i].compare_exchange_strong(expected, true) == true) { + break; + } ++i; if (i >= RuntimeError::MAX_RUNTIME_ERRORS) { throw std::runtime_error("Too many o2::framework::runtime_error thrown without proper cleanup."); @@ -78,11 +81,18 @@ RuntimeErrorRef runtime_error_f(const char* format, ...) RuntimeErrorRef runtime_error(const char* s) { int i = 0; - bool expected = false; - while (gErrorBooking[i].compare_exchange_strong(expected, true) == false) { + while (true) { + bool expected = false; + if (gErrorBooking[i].compare_exchange_strong(expected, true) == true) { + break; + } ++i; + if (i >= RuntimeError::MAX_RUNTIME_ERRORS) { + throw std::runtime_error("Too many o2::framework::runtime_error thrown without proper cleanup."); + } } - strncpy(gError[i].what, s, RuntimeError::MAX_RUNTIME_ERROR_SIZE); + strncpy(gError[i].what, s, RuntimeError::MAX_RUNTIME_ERROR_SIZE - 1); + gError[i].what[RuntimeError::MAX_RUNTIME_ERROR_SIZE - 1] = 0; gError[i].maxBacktrace = canDumpBacktrace() ? backtrace(gError[i].backtrace, BacktraceHelpers::MAX_BACKTRACE_SIZE) : 0; return RuntimeErrorRef{i}; } 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/CMakeLists.txt b/Framework/GUISupport/CMakeLists.txt index 61519c21dc20c..8e67da3e53e15 100644 --- a/Framework/GUISupport/CMakeLists.txt +++ b/Framework/GUISupport/CMakeLists.txt @@ -20,6 +20,7 @@ o2_add_library(FrameworkGUISupport src/PaletteHelpers.cxx src/SpyService.cxx src/SpyServiceHelpers.cxx + src/InspectorHelpers.cxx PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src PUBLIC_LINK_LIBRARIES O2::Framework AliceO2::DebugGUI) diff --git a/Framework/GUISupport/src/FrameworkGUIDataRelayerUsage.cxx b/Framework/GUISupport/src/FrameworkGUIDataRelayerUsage.cxx index c39e268fa90a7..86558d22b973d 100644 --- a/Framework/GUISupport/src/FrameworkGUIDataRelayerUsage.cxx +++ b/Framework/GUISupport/src/FrameworkGUIDataRelayerUsage.cxx @@ -43,7 +43,7 @@ struct HeatMapHelper { { float padding = 1; // add slider to scroll between the grid display windows - size_t nw = getNumRecords() / WND; + size_t nw = getNumRecords() < WND ? 1 : getNumRecords() / WND; ImGui::PushItemWidth(sizeHint.x); ImGui::SliderInt("##window", &v, 1, nw, "wnd: %d", ImGuiSliderFlags_AlwaysClamp); ImVec2 sliderMin = ImGui::GetItemRectMin(); @@ -51,7 +51,7 @@ struct HeatMapHelper { constexpr float MAX_BOX_X_SIZE = 16.f; constexpr float MAX_BOX_Y_SIZE = 16.f; - ImVec2 size = ImVec2(sizeHint.x, std::min(sizeHint.y, MAX_BOX_Y_SIZE * getNumItems(0) + 2)); + ImVec2 size = ImVec2(sizeHint.x, std::min(sizeHint.y, MAX_BOX_Y_SIZE * getNumInputs() + 2)); ImU32 BORDER_COLOR = ImColor(200, 200, 200, 255); ImU32 BACKGROUND_COLOR = ImColor(20, 20, 20, 255); ImU32 BORDER_COLOR_A = ImColor(200, 200, 200, 0); @@ -75,19 +75,22 @@ struct HeatMapHelper { const static auto colorE = ImColor(ImVec4{0, 0, 0, 0}); drawList->PrimReserve(nw * 6, nw * 4); - for (size_t iw = 0; iw < nw; ++iw) { - ImVec2 xOffset{iw * xsz + 2 * padding, 0}; + for (size_t iw = 1; iw <= nw; ++iw) { + ImVec2 xOffset{(iw - 1) * xsz + 2 * padding, 0}; ImVec2 xSize{xsz - 2 * padding, 0}; ImVec2 yOffset{0, 2 * padding}; - ImVec2 ySize{0, 16 - 4 * padding}; - bool active = 0; - for (size_t ir = iw; ir < ((iw + WND > getNumRecords()) ? getNumRecords() : iw + WND); ++ir) { - for (size_t i = 0; i < getNumItems(ir); ++i) { - active = getValue(*getItem(ir, i)) > 0; + ImVec2 ySize{0, MAX_BOX_Y_SIZE - 4 * padding}; + bool active = false; + for (size_t ir = (iw - 1) * WND; ir < ((iw * WND > getNumRecords()) ? getNumRecords() : iw * WND); ++ir) { + for (size_t i = 0; i < getNumItems(getRecord(ir)); ++i) { + active = getValue(*getItem(getRecord(ir), i)) > 0; if (active) { break; } } + if (active) { + break; + } } drawList->PrimRect( xOffset + yOffset + winPos, @@ -96,47 +99,46 @@ struct HeatMapHelper { } // display the grid - size_t recordsWindow = v * WND; auto boxSizeX = std::min(size.x / WND, MAX_BOX_X_SIZE); - auto numInputs = getNumInputs(); + auto boxSizeY = std::min(size.y / getNumInputs(), MAX_BOX_Y_SIZE); + winPos = ImGui::GetCursorScreenPos() + ImVec2{0, 7}; - ImGui::InvisibleButton("sensible area", ImVec2(size.x, size.y)); + ImGui::InvisibleButton("sensitive area", ImVec2(size.x, size.y)); if (ImGui::IsItemHovered()) { auto pos = ImGui::GetMousePos() - winPos; - auto slot = (v - 1) * WND + std::lround(std::trunc(pos.x / size.x * WND)); - auto row = std::lround(std::trunc(pos.y / size.y * numInputs)); + auto slot = (v - 1) * WND + std::lround(std::trunc(pos.x / boxSizeX)); + auto row = std::lround(std::trunc(pos.y / boxSizeY)); describeCell(row, slot); } + // background drawList->AddRectFilled( ImVec2(0., 0.) + winPos, ImVec2{size.x, size.y} + winPos, BACKGROUND_COLOR); + // border drawList->AddRect( ImVec2(0. - 1, -1) + winPos, ImVec2{size.x + 1, size.y - 1} + winPos, BORDER_COLOR); - size_t totalRects = 0; - for (size_t ri = (v - 1) * WND; ri < recordsWindow; ri++) { - auto record = getRecord(ri); - totalRects += getNumItems(record); - } - - drawList->PrimReserve(totalRects * 6, totalRects * 4); - for (size_t ri = (v - 1) * WND; ri < recordsWindow; ri++) { + // heatmap + size_t totalPrims = WND * getNumInputs(); + drawList->PrimReserve(totalPrims * 6, totalPrims * 4); + for (size_t ri = (v - 1) * WND; ri < (((size_t)(v)*WND > getNumRecords()) ? getNumRecords() : v * WND); ++ri) { auto record = getRecord(ri); - ImVec2 xOffset{((ri - (v - 1) * WND) * boxSizeX) + padding, 0}; + ImVec2 xOffset{((float)(ri - (v - 1) * WND) * boxSizeX) + padding, 0}; ImVec2 xSize{boxSizeX - 2 * padding, 0}; - auto me = getNumItems(record); - auto boxSizeY = std::min(size.y / me, MAX_BOX_Y_SIZE); - for (size_t mi = 0; mi < me; mi++) { - ImVec2 yOffSet{0, (mi * boxSizeY) + padding}; + + for (auto mi = 0U; mi < getNumItems(record); mi++) { + ImVec2 yOffSet{0, ((float)mi * boxSizeY) + padding}; ImVec2 ySize{0, boxSizeY - 2 * padding}; + ImVec2 A = xOffset + yOffSet + winPos; + ImVec2 B = xOffset + xSize + yOffSet + ySize + winPos; + drawList->PrimRect( - xOffset + yOffSet + winPos, - xOffset + xSize + yOffSet + ySize + winPos, + A, B, getColor(getValue(*getItem(record, mi)))); } } @@ -233,7 +235,7 @@ void displayDataRelayer(DeviceMetricsInfo const& /*metrics*/, continue; } if (i == (size_t)row) { - ImGui::Text("%d %.*s (%s)", row, int(end - input), input, InspectorHelpers::getLifeTimeStr(spec.inputs[i].matcher.lifetime).c_str()); + ImGui::Text("%d %.*s (%s)", row, int(end - input), input, InspectorHelpers::getLifeTimeStr(spec.inputs[i].matcher.lifetime)); break; } ++i; diff --git a/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx b/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx index 9b2a13c07987d..b29e024ec641e 100644 --- a/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx +++ b/Framework/GUISupport/src/FrameworkGUIDeviceInspector.cxx @@ -79,7 +79,8 @@ void deviceStateTable(DataProcessingStates const& states) } } -void deviceInfoTable(char const* label, ProcessingStateId id, DataProcessingStates const& states, std::variant, std::vector> routes, DeviceMetricsInfo const& metrics) +template +void deviceInfoTable(char const* label, ProcessingStateId id, DataProcessingStates const& states, Routes const& routes, DeviceMetricsInfo const& metrics) { // Find the state spec associated to data_queries auto& view = states.statesViews[(int)id]; @@ -95,17 +96,10 @@ void deviceInfoTable(char const* label, ProcessingStateId id, DataProcessingStat if ((end - input) == 0) { continue; } - auto getLifetime = [&routes, &i]() -> Lifetime { - if (std::get_if>(&routes)) { - return std::get>(routes)[i].matcher.lifetime; - } else { - return std::get>(routes)[i].matcher.lifetime; - } - }; - ImGui::Text("%zu: %.*s (%s)", i, int(end - input), input, InspectorHelpers::getLifeTimeStr(getLifetime()).c_str()); + ImGui::Text("%zu: %.*s (%s)", i, int(end - input), input, InspectorHelpers::getLifeTimeStr(routes[i].matcher.lifetime)); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::Text("%zu: %.*s (%s)", i, int(end - input), input, InspectorHelpers::getLifeTimeStr(getLifetime()).c_str()); + ImGui::Text("%zu: %.*s (%s)", i, int(end - input), input, InspectorHelpers::getLifeTimeStr(routes[i].matcher.lifetime)); ImGui::EndTooltip(); } input = end + 1; @@ -335,6 +329,10 @@ void displayDeviceInspector(DeviceSpec const& spec, control.controller->write("/shm-offer 1000", strlen("/shm-offer 1000")); } + if (ImGui::Button("Offer timeslices")) { + control.controller->write("/timeslice-offer 1", strlen("/timeslice-offer 1")); + } + if (control.requestedState > info.providedState) { ImGui::Text(ICON_FA_CLOCK_O); } else { @@ -346,8 +344,8 @@ void displayDeviceInspector(DeviceSpec const& spec, } deviceStateTable(states); - deviceInfoTable("Inputs:", ProcessingStateId::DATA_QUERIES, states, std::variant, std::vector>(spec.inputs), metrics); - deviceInfoTable("Outputs:", ProcessingStateId::OUTPUT_MATCHERS, states, std::variant, std::vector>(spec.outputs), metrics); + deviceInfoTable("Inputs:", ProcessingStateId::DATA_QUERIES, states, spec.inputs, metrics); + deviceInfoTable("Outputs:", ProcessingStateId::OUTPUT_MATCHERS, states, spec.outputs, metrics); configurationTable(info.currentConfig, info.currentProvenance); optionsTable("Workflow Options", metadata.workflowOptions, control); if (ImGui::CollapsingHeader("Labels", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -402,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/InspectorHelpers.cxx b/Framework/GUISupport/src/InspectorHelpers.cxx new file mode 100644 index 0000000000000..23e74c964e531 --- /dev/null +++ b/Framework/GUISupport/src/InspectorHelpers.cxx @@ -0,0 +1,40 @@ +// Copyright 2019-2025 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 "InspectorHelpers.h" + +namespace o2::framework +{ +char const* InspectorHelpers::getLifeTimeStr(Lifetime lifetime) +{ + switch (lifetime) { + case Lifetime::Timeframe: + return "Timeframe"; + case Lifetime::Condition: + return "Condition"; + case Lifetime::Sporadic: + return "Sporadic"; + case Lifetime::Transient: + return "Transient"; + case Lifetime::Timer: + return "Timer"; + case Lifetime::Enumeration: + return "Enumeration"; + case Lifetime::Signal: + return "Signal"; + case Lifetime::Optional: + return "Optional"; + case Lifetime::OutOfBand: + return "OutOfBand"; + } + return "none"; +}; +} // namespace o2::framework diff --git a/Framework/GUISupport/src/InspectorHelpers.h b/Framework/GUISupport/src/InspectorHelpers.h index 124c714f54df5..193486fc91dbc 100644 --- a/Framework/GUISupport/src/InspectorHelpers.h +++ b/Framework/GUISupport/src/InspectorHelpers.h @@ -1,4 +1,4 @@ -// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// Copyright 2019-2025 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. // @@ -11,8 +11,6 @@ #ifndef O2_FRAMEWORK_INSPECTORHELPERS_H_ #define O2_FRAMEWORK_INSPECTORHELPERS_H_ -#include - #include "Framework/Lifetime.h" namespace o2::framework @@ -20,30 +18,7 @@ namespace o2::framework /// A helper class for inpsection of device information struct InspectorHelpers { - static const std::string getLifeTimeStr(Lifetime lifetime) - { - switch (lifetime) { - case Lifetime::Timeframe: - return "Timeframe"; - case Lifetime::Condition: - return "Condition"; - case Lifetime::Sporadic: - return "Sporadic"; - case Lifetime::Transient: - return "Transient"; - case Lifetime::Timer: - return "Timer"; - case Lifetime::Enumeration: - return "Enumeration"; - case Lifetime::Signal: - return "Signal"; - case Lifetime::Optional: - return "Optional"; - case Lifetime::OutOfBand: - return "OutOfBand"; - } - return "none"; - }; + static const char* getLifeTimeStr(Lifetime lifetime); }; } // namespace o2::framework 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/CMakeLists.txt b/Framework/TestWorkflows/CMakeLists.txt index f5d18183c3705..d2b98419043bf 100644 --- a/Framework/TestWorkflows/CMakeLists.txt +++ b/Framework/TestWorkflows/CMakeLists.txt @@ -46,6 +46,10 @@ o2_add_dpl_workflow(analysis-ccdb PUBLIC_LINK_LIBRARIES O2::DataFormatsTOF COMPONENT_NAME TestWorkflows) +o2_add_dpl_workflow(analysis-emb + SOURCES src/o2TestMultisource.cxx + COMPONENT_NAME TestWorkflows) + o2_add_dpl_workflow(two-timers SOURCES src/o2TwoTimers.cxx COMPONENT_NAME TestWorkflows) diff --git a/Framework/TestWorkflows/src/o2ParallelWorkflow.cxx b/Framework/TestWorkflows/src/o2ParallelWorkflow.cxx index 841f4a8f2b9bd..bdc08ad45ea24 100644 --- a/Framework/TestWorkflows/src/o2ParallelWorkflow.cxx +++ b/Framework/TestWorkflows/src/o2ParallelWorkflow.cxx @@ -9,7 +9,12 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. +#include "Framework/ConcreteDataMatcher.h" #include "Framework/ConfigParamSpec.h" +#include "Framework/CompletionPolicy.h" +#include "Framework/CompletionPolicyHelpers.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/Logger.h" #include #include @@ -29,13 +34,16 @@ void customize(std::vector& workflowOptions) ConfigParamSpec{"3-layer-pipelining", VariantType::Int, 1, {timeHelp}}); } +void customize(std::vector& policies) +{ + policies = { + CompletionPolicyHelpers::consumeWhenPastOldestPossibleTimeframe("merger-policy", [](auto const&) -> bool { return true; })}; +} + #include "Framework/runDataProcessing.h" #include "Framework/DataProcessorSpec.h" #include "Framework/DataSpecUtils.h" #include "Framework/ParallelContext.h" -#include "Framework/ControlService.h" - -#include "Framework/Logger.h" #include @@ -43,22 +51,24 @@ using DataHeader = o2::header::DataHeader; DataProcessorSpec templateProcessor() { - return DataProcessorSpec{"some-processor", { - InputSpec{"x", "TST", "A", 0, Lifetime::Timeframe}, - }, - { + return DataProcessorSpec{.name = "some-processor", + .inputs = { + InputSpec{"x", "TST", "A", 0, Lifetime::Timeframe}, + }, + .outputs = { OutputSpec{"TST", "P", 0, Lifetime::Timeframe}, }, // The producer is stateful, we use a static for the state in this // particular case, but a Singleton or a captured new object would // work as well. - AlgorithmSpec{[](InitContext& setup) { + .algorithm = AlgorithmSpec{[](InitContext& setup) { srand(setup.services().get().index1D()); return [](ProcessingContext& ctx) { // Create a single output. size_t index = ctx.services().get().index1D(); - auto& aData = ctx.outputs().make( + auto& i = ctx.outputs().make( Output{"TST", "P", static_cast(index)}, 1); + i[0] = index; std::this_thread::sleep_for(std::chrono::seconds(rand() % 5)); }; }}}; @@ -86,34 +96,43 @@ WorkflowSpec defineDataProcessing(ConfigContext const& config) outputSpecs.emplace_back("TST", "A", ssi); } - workflow.push_back(DataProcessorSpec{"reader", {}, outputSpecs, AlgorithmSpec{[jobs](InitContext& initCtx) { - return [jobs](ProcessingContext& ctx) { - for (size_t ji = 0; ji < jobs; ++ji) { - ctx.outputs().make(Output{"TST", "A", static_cast(ji)}, - 1); - } - }; - }}}); + workflow.push_back(DataProcessorSpec{ + .name = "reader", + .outputs = outputSpecs, + .algorithm = AlgorithmSpec{[jobs](InitContext& initCtx) { + return [jobs](ProcessingContext& ctx) { + static int count = 0; + for (size_t ji = 0; ji < jobs; ++ji) { + int& i = ctx.outputs().make(Output{"TST", "A", static_cast(ji)}); + i = count * 100 + ji; + } + count++; + }; + }}}); workflow.push_back(timePipeline(DataProcessorSpec{ - "merger", - mergeInputs(InputSpec{"x", "TST", "P"}, - jobs, - [](InputSpec& input, size_t index) { - DataSpecUtils::updateMatchingSubspec(input, index); - }), - {OutputSpec{{"out"}, "TST", "M"}}, - AlgorithmSpec{[](InitContext& setup) { + .name = "merger", + .inputs = {InputSpec{"all", ConcreteDataTypeMatcher{"TST", "P"}}}, + .outputs = {OutputSpec{{"out"}, "TST", "M"}}, + .algorithm = AlgorithmSpec{[](InitContext& setup) { return [](ProcessingContext& ctx) { + LOGP(info, "Run"); + for (const auto& input : o2::framework::InputRecordWalker(ctx.inputs())) { + if (input.header == nullptr) { + LOGP(error, "Missing header"); + continue; + } + int record = *(int*)input.payload; + LOGP(info, "Record {}", record); + } ctx.outputs().make(OutputRef("out", 0), 1); }; }}}, stages)); workflow.push_back(DataProcessorSpec{ - "writer", - {InputSpec{"x", "TST", "M"}}, - {}, - AlgorithmSpec{[](InitContext& setup) { + .name = "writer", + .inputs = {InputSpec{"x", "TST", "M"}}, + .algorithm = AlgorithmSpec{[](InitContext& setup) { return [](ProcessingContext& ctx) { }; }}}); 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/o2TestHistograms.cxx b/Framework/TestWorkflows/src/o2TestHistograms.cxx index 61710e1f63d5f..9c2cba35b9156 100644 --- a/Framework/TestWorkflows/src/o2TestHistograms.cxx +++ b/Framework/TestWorkflows/src/o2TestHistograms.cxx @@ -16,8 +16,6 @@ #include "Framework/runDataProcessing.h" #include "Framework/AnalysisTask.h" #include -#include -#include using namespace o2; using namespace o2::framework; @@ -43,6 +41,28 @@ struct EtaAndClsHistogramsSimple { Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; Filter trackFilter = o2::aod::track::pt < 10.f; + HistogramRegistry registry{ + "registry", + { + {"a/b/eta", "#Eta", {HistType::kTH1F, {{100, -2.0, 2.0}}}}, // + {"a/phi", "#Phi", {HistType::kTH1D, {{102, 0, 2 * M_PI}}}}, // + {"c/pt", "p_{T}", {HistType::kTH1D, {{1002, -0.01, 50.1}}}}, // + {"ptToPt", "#ptToPt", {HistType::kTH2F, {{100, -0.01, 10.01}, {100, -0.01, 10.01}}}} // + } // + }; + + HistogramRegistry registry2{ + "registry2", + { + {"a/foo/b/eta", "#Eta", {HistType::kTH1F, {{100, -2.0, 2.0}}}}, // + {"fii/c/hpt", "p_{T}", {HistType::kTH1D, {{1002, -0.01, 50.1}}}}, // + {"a/foobar/phi", "#Phi", {HistType::kTH1D, {{102, 0, 2 * M_PI}}}}, // + {"fifi/ptToPt", "#ptToPt", {HistType::kTH2F, {{100, -0.01, 10.01}, {100, -0.01, 10.01}}}} // + }, + OutputObjHandlingPolicy::AnalysisObject, + false, + true}; + void init(InitContext&) { if (!trackFilterString->empty()) { @@ -50,12 +70,17 @@ struct EtaAndClsHistogramsSimple { } } - void process(soa::Filtered const& tracks, aod::FT0s const&, aod::StoredTracksFrom> const& ortherTracks) + void process(soa::Filtered const& tracks, aod::FT0s const&) { LOGP(info, "Invoking the simple one"); for (auto& track : tracks) { etaClsH->Fill(track.eta(), track.pt()); skimEx(track.pt(), track.eta()); + + registry.fill(HIST("a/b/eta"), track.eta()); + registry.fill(HIST("a/phi"), track.phi()); + registry.fill(HIST("c/pt"), track.pt()); + registry.fill(HIST("ptToPt"), track.pt(), track.signed1Pt()); } } }; @@ -66,6 +91,16 @@ struct EtaAndClsHistogramsIUSimple { Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; Filter trackFilter = o2::aod::track::pt < 10.f; + HistogramRegistry registry{ + "registry", + { + {"a/b/eta", "#Eta", {HistType::kTH1F, {{100, -2.0, 2.0}}}}, // + {"a/phi", "#Phi", {HistType::kTH1D, {{102, 0, 2 * M_PI}}}}, // + {"c/pt", "p_{T}", {HistType::kTH1D, {{1002, -0.01, 50.1}}}}, // + {"ptToPt", "#ptToPt", {HistType::kTH2F, {{100, -0.01, 10.01}, {100, -0.01, 10.01}}}} // + } // + }; + void init(InitContext&) { if (!trackFilterString->empty()) { @@ -73,18 +108,34 @@ struct EtaAndClsHistogramsIUSimple { } } - void process(soa::Filtered const& tracks, aod::FT0s const&, aod::TracksIUFrom> const& otherTracks) + void process(soa::Filtered const& tracks, aod::FT0s const&) { LOGP(info, "Invoking the simple one IU"); for (auto& track : tracks) { etaClsH->Fill(track.eta(), track.pt()); skimEx(track.pt(), track.eta()); + + registry.fill(HIST("a/b/eta"), track.eta()); + registry.fill(HIST("a/phi"), track.phi()); + registry.fill(HIST("c/pt"), track.pt()); + registry.fill(HIST("ptToPt"), track.pt(), track.signed1Pt()); } } }; struct EtaAndClsHistogramsFull { OutputObj etaClsH{TH3F("eta_vs_cls_vs_sigmapT", "#eta vs N_{cls} vs sigma_{1/pT}", 102, -2.01, 2.01, 160, -0.5, 159.5, 100, 0, 10)}; + + HistogramRegistry registry{ + "registry", + { + {"a/b/eta", "#Eta", {HistType::kTH1F, {{100, -2.0, 2.0}}}}, // + {"a/phi", "#Phi", {HistType::kTH1D, {{102, 0, 2 * M_PI}}}}, // + {"c/pt", "p_{T}", {HistType::kTH1D, {{1002, -0.01, 50.1}}}}, // + {"ptToPt", "#ptToPt", {HistType::kTH2F, {{100, -0.01, 10.01}, {100, -0.01, 10.01}}}} // + } // + }; + Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; Filter trackFilter = o2::aod::track::pt < 10.f; @@ -100,6 +151,11 @@ struct EtaAndClsHistogramsFull { LOGP(info, "Invoking the run 3 one"); for (auto& track : tracks) { etaClsH->Fill(track.eta(), track.tpcNClsFindable(), track.sigma1Pt()); + + registry.fill(HIST("a/b/eta"), track.eta()); + registry.fill(HIST("a/phi"), track.phi()); + registry.fill(HIST("c/pt"), track.pt()); + registry.fill(HIST("ptToPt"), track.pt(), track.signed1Pt()); } } }; diff --git a/Framework/TestWorkflows/src/o2TestMultisource.cxx b/Framework/TestWorkflows/src/o2TestMultisource.cxx new file mode 100644 index 0000000000000..d52a25f67e98f --- /dev/null +++ b/Framework/TestWorkflows/src/o2TestMultisource.cxx @@ -0,0 +1,45 @@ +// 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. +/// +/// \brief Tests that the same tables from different origins are routed correctly. +/// Requires two input files, .root and _EMB.root, that contain +/// same number of DFs with the same names. +/// \author +/// \since + +#include "Framework/runDataProcessing.h" +#include "Framework/AnalysisTask.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::framework::expressions; + +namespace o2::aod +{ +O2ORIGIN("EMB"); +using TracksPlus = soa::Join; +template +using TracksPlusFrom = soa::Join, StoredTracksExtra_002From>; +} // namespace o2::aod + +struct TestEmbeddingSubscription { + void process(aod::BCs const& bcs, aod::BCsFrom> const& bcse, + aod::TracksIU const& tracks, aod::TracksIUFrom> const& trackse) + { + 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()); + } +}; + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + return {adaptAnalysisTask(cfgc)}; +} diff --git a/Framework/TestWorkflows/src/o2_sim_tpc.cxx b/Framework/TestWorkflows/src/o2_sim_tpc.cxx deleted file mode 100644 index 4587c0fcb831f..0000000000000 --- a/Framework/TestWorkflows/src/o2_sim_tpc.cxx +++ /dev/null @@ -1,157 +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 "Framework/DataRefUtils.h" -#include "Framework/WorkflowSpec.h" -#include -#include "Framework/RootFileService.h" -#include "Framework/AlgorithmSpec.h" -#include "Framework/ConfigParamRegistry.h" - -#include "Framework/Logger.h" - -#include "FairRunSim.h" -#include -#include "FairRuntimeDb.h" -#include "FairPrimaryGenerator.h" -#include "FairBoxGenerator.h" -#include "FairParRootFileIo.h" - -#include "DetectorsPassive/Cave.h" -#include "Field/MagneticField.h" - -#include "DetectorsPassive/Cave.h" -#include "Generators/GeneratorFromFile.h" -#include "TPCSimulation/Detector.h" -#include "Framework/OutputSpec.h" -#include - -using namespace o2::framework; - -#define BOX_GENERATOR 1 - -namespace o2 -{ -namespace workflows -{ - -DataProcessorSpec sim_tpc() -{ - return { - "sim_tpc", - Inputs{}, - Outputs{OutputSpec{"TPC", "GEN"}}, - AlgorithmSpec{ - [](InitContext& setup) { - int nEvents = setup.options().get("nEvents"); - auto mcEngine = setup.options().get("mcEngine"); - - // FIXME: this should probably be part of some generic - // FairRunInitSpec - TString dir = getenv("VMCWORKDIR"); - TString geom_dir = dir + "/Detectors/Geometry/"; - gSystem->Setenv("GEOMPATH", geom_dir.Data()); - - TString tut_configdir = dir + "/Detectors/gconfig"; - gSystem->Setenv("CONFIG_DIR", tut_configdir.Data()); - - // Requiring a file is something which requires IO, and it's therefore - // delegated to the framework - auto& rfm = setup.services().get(); - // FIXME: We should propably have a service for FairRunSim, rather than - // for the root files themselves... - // Output file name - auto outFile = rfm.format("AliceO2_%s.tpc.mc_%i_event.root", mcEngine.c_str(), nEvents); - - // Parameter file name - auto parFile = rfm.format("AliceO2_%s.tpc.mc_%i_event.root", mcEngine.c_str(), nEvents); - - // Create simulation run - FairRunSim* run = new FairRunSim(); - - run->SetName(mcEngine.c_str()); - run->SetSink(new FairRootFileSink(outFile.c_str())); // Output file - FairRuntimeDb* rtdb = run->GetRuntimeDb(); - - // Create media - run->SetMaterials("media.geo"); // Materials - - // Create geometry - o2::passive::Cave* cave = new o2::passive::Cave("CAVE"); - cave->SetGeometryFileName("cave.geo"); - run->AddModule(cave); - - o2::field::MagneticField* magField = new o2::field::MagneticField("Maps", "Maps", -1., -1., o2::field::MagFieldParam::k5kG); - run->SetField(magField); - - // ===| Add TPC |============================================================ - o2::tpc::Detector* tpc = new o2::tpc::Detector(kTRUE); - tpc->SetGeoFileName("TPCGeometry.root"); - run->AddModule(tpc); - - // Create PrimaryGenerator - FairPrimaryGenerator* primGen = new FairPrimaryGenerator(); -#ifdef BOX_GENERATOR - FairBoxGenerator* boxGen = new FairBoxGenerator(211, 10); /*protons*/ - - //boxGen->SetThetaRange(0.0, 90.0); - boxGen->SetEtaRange(-0.9, 0.9); - boxGen->SetPRange(0.1, 5); - boxGen->SetPhiRange(0., 360.); - boxGen->SetDebug(kTRUE); - - primGen->AddGenerator(boxGen); -#else - // reading the events from a kinematics file (produced by AliRoot) - auto extGen = new o2::eventgen::GeneratorFromFile(params.get("extKinFile")); - extGen->SetStartEvent(params.get("startEvent")); - primGen->AddGenerator(extGen); -#endif - - run->SetGenerator(primGen); - - // store track trajectories - // run->SetStoreTraj(kTRUE); - - // Initialize simulation run - run->Init(); - - // Runtime database - Bool_t kParameterMerged = kTRUE; - FairParRootFileIo* parOut = new FairParRootFileIo(kParameterMerged); - parOut->open(parFile.c_str()); - rtdb->setOutput(parOut); - rtdb->saveOutput(); - rtdb->print(); - run->Run(nEvents); - - static bool once = true; - - // This is the actual inner loop for the device - return [run, nEvents](ProcessingContext& ctx) { - if (!once) { - run->Run(nEvents); - once = true; - } else { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - // FIXME: After we run we should readback events - // and push them as messages, for the next stage of - // processing. - }; - }}, - Options{ - {"mcEngine", VariantType::String, "TGeant3", {"Engine to use"}}, - {"nEvents", VariantType::Int, 10, {"Events to process"}}, - {"extKinFile", VariantType::String, "Kinematics.root", {"name of kinematics file for event generator from file (when applicable)"}}, - {"startEvent", VariantType::Int, 2, {"Events to skip"}}}}; -}; -} // namespace workflows -} // namespace o2 diff --git a/Framework/TestWorkflows/src/test_o2ITSCluserizer.cxx b/Framework/TestWorkflows/src/test_o2ITSCluserizer.cxx deleted file mode 100644 index d6d3cb1242f7c..0000000000000 --- a/Framework/TestWorkflows/src/test_o2ITSCluserizer.cxx +++ /dev/null @@ -1,32 +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 "Framework/DataRefUtils.h" -#include "Framework/ServiceRegistry.h" -#include "Framework/runDataProcessing.h" -#include -// FIXME: this should not be needed as the framework should be able to -// decode TClonesArray by itself. -#include "Framework/TMessageSerializer.h" -#include "o2_sim_its_ALP3.h" -#include "Framework/Logger.h" -#include -#include - -using namespace o2::framework; -using namespace o2::workflows; - -// This is how you can define your processing in a declarative way -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ - return WorkflowSpec{ - sim_its_ALP3(), - }; -} diff --git a/Framework/TestWorkflows/src/test_o2TPCSimulation.cxx b/Framework/TestWorkflows/src/test_o2TPCSimulation.cxx deleted file mode 100644 index 403ad8bc7127b..0000000000000 --- a/Framework/TestWorkflows/src/test_o2TPCSimulation.cxx +++ /dev/null @@ -1,31 +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 "Framework/DataRefUtils.h" -#include "Framework/ServiceRegistry.h" -#include "Framework/runDataProcessing.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/DataRef.h" -// FIXME: this should not be needed as the framework should be able to -// decode TClonesArray by itself. -#include "Framework/TMessageSerializer.h" -#include "o2_sim_tpc.h" -#include "Framework/Logger.h" - -using namespace o2::framework; -using namespace o2::workflows; - -// This is how you can define your processing in a declarative way -WorkflowSpec defineDataProcessing(ConfigContext const& specs) -{ - return WorkflowSpec{ - sim_tpc(), - }; -} diff --git a/Framework/Utils/include/DPLUtils/DPLRawParser.h b/Framework/Utils/include/DPLUtils/DPLRawParser.h index e1732ef70550a..5fa0775025deb 100644 --- a/Framework/Utils/include/DPLUtils/DPLRawParser.h +++ b/Framework/Utils/include/DPLUtils/DPLRawParser.h @@ -76,6 +76,7 @@ class DPLRawParser void setMaxFailureMessages(size_t n) { mMaxFailureMessages = n; } void setExtFailureCounter(size_t* cnt) { mExtFailureCounter = cnt; } static void setCheckIncompleteHBF(bool v) { rawparser_type::setCheckIncompleteHBF(v); } + static void setErrorMode(int v) { rawparser_type::setErrorMode(v); } // this is a dummy default buffer used to initialize the RawParser in the iterator // constructor diff --git a/Framework/Utils/include/DPLUtils/RawParser.h b/Framework/Utils/include/DPLUtils/RawParser.h index c1ba1ef4802b2..fa45cf79b7568 100644 --- a/Framework/Utils/include/DPLUtils/RawParser.h +++ b/Framework/Utils/include/DPLUtils/RawParser.h @@ -649,6 +649,11 @@ class RawParser raw_parser::RawParserHelper::sCheckIncompleteHBF = v; } + static void setErrorMode(int v) + { + raw_parser::RawParserHelper::sErrorMode = v; + } + private: raw_parser::ConcreteParserVariants mParser; }; diff --git a/Framework/Utils/include/DPLUtils/RootTreeWriter.h b/Framework/Utils/include/DPLUtils/RootTreeWriter.h index 0161c67396543..b937a83f5972a 100644 --- a/Framework/Utils/include/DPLUtils/RootTreeWriter.h +++ b/Framework/Utils/include/DPLUtils/RootTreeWriter.h @@ -714,7 +714,7 @@ class RootTreeWriter impl.start = &(data[0]); impl.end = &(data[data.size() - 1]) + 1; // end pointer (beyond last element) impl.cap = impl.end; - std::memcpy(&v, &impl, sizeof(VecBase)); + std::memcpy((void*)&v, (const void*)&impl, sizeof(VecBase)); }; // if the value type is messagable and has a ROOT dictionary, two serialization methods are possible diff --git a/Framework/Utils/test/DPLBroadcasterMerger.cxx b/Framework/Utils/test/DPLBroadcasterMerger.cxx deleted file mode 100644 index bf793275d2f3f..0000000000000 --- a/Framework/Utils/test/DPLBroadcasterMerger.cxx +++ /dev/null @@ -1,147 +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 Gabriele Gaetano Fronzé, gfronze@cern.ch - -#include -#include "DPLBroadcasterMerger.h" -#include "DPLUtils/Utils.h" -#include "Framework/DataProcessorSpec.h" -#include "Framework/ControlService.h" -#include "Framework/DataRefUtils.h" -#include "random" -#include "Framework/Logger.h" -#include - -namespace o2f = o2::framework; - -namespace o2::workflows -{ - -o2f::Inputs noInputs{}; -o2f::Outputs noOutputs{}; - -o2f::DataProcessorSpec defineGenerator(o2f::OutputSpec usrOutput) -{ - return {"Generator", // Device name - noInputs, // No inputs for a generator - o2f::Outputs{usrOutput}, // One simple output - - o2f::AlgorithmSpec{[usrOutput](o2f::InitContext&) { - int msgCounter = 0; - auto msgCounter_shptr = std::make_shared(msgCounter); - auto usrOutput_shptr = std::make_shared(getOutput(usrOutput)); - - LOG(info) << ">>>>>>>>>>>>>> Generator initialised"; - - // Processing context in captured from return on InitCallback - return [usrOutput_shptr, msgCounter_shptr](o2f::ProcessingContext& ctx) { - int msgIndex = (*msgCounter_shptr)++; - if (msgIndex > 10) { - ctx.services().get().endOfStream(); - } - LOG(info) << ">>> MSG:" << msgIndex; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - - LOG(info) << ">>> Preparing MSG:" << msgIndex; - - auto& outputMsg = - ctx.outputs().newChunk(*usrOutput_shptr, (msgIndex + 1) * sizeof(uint32_t) / sizeof(char)); - - LOG(info) << ">>> Preparing1 MSG:" << msgIndex; - - auto payload = reinterpret_cast(outputMsg.data()); - - payload[0] = msgIndex; - - LOG(info) << ">>> Preparing2 MSG:" << msgIndex; - - for (int k = 0; k < msgIndex; ++k) { - payload[k + 1] = (uint32_t)32; - LOG(info) << ">>>>\t" << payload[k + 1]; - } - - return; - }; - }}}; -} - -o2f::DataProcessorSpec definePipeline(std::string devName, o2f::InputSpec usrInput, o2f::OutputSpec usrOutput) -{ - return {devName, // Device name - o2f::Inputs{usrInput}, // No inputs, for the moment - o2f::Outputs{usrOutput}, o2f::AlgorithmSpec{[usrOutput](o2f::InitContext&) { - auto output_sharedptr = std::make_shared(getOutput(usrOutput)); - - // Processing context in captured from return on InitCallback - return [output_sharedptr](o2f::ProcessingContext& ctx) { - auto inputMsg = ctx.inputs().getByPos(0); - auto msgSize = o2::framework::DataRefUtils::getPayloadSize(inputMsg); - - auto& fwdMsg = ctx.outputs().newChunk((*output_sharedptr), msgSize); - std::memcpy(fwdMsg.data(), inputMsg.payload, msgSize); - }; - }}}; -} - -o2f::DataProcessorSpec defineSink(o2f::InputSpec usrInput) -{ - return {"Sink", // Device name - o2f::Inputs{usrInput}, // No inputs, for the moment - noOutputs, - - o2f::AlgorithmSpec{[](o2f::InitContext&) { - // Processing context in captured from return on InitCallback - return [](o2f::ProcessingContext& ctx) { - LOG(info) << "Received message "; - - auto inputMsg = ctx.inputs().getByPos(0); - auto payload = reinterpret_cast(inputMsg.payload); - - LOG(info) << "Received message containing" << payload[0] << "elements"; - - for (int j = 0; j < payload[0]; ++j) { - LOG(info) << payload[j + 1] << "\t"; - } - LOG(info); - }; - }}}; -} - -o2::framework::WorkflowSpec DPLBroadcasterMergerWorkflow() -{ - auto lspec = o2f::WorkflowSpec(); - - // A generator of data - lspec.emplace_back(defineGenerator(o2f::OutputSpec{"TST", "ToBC", 0, o2f::Lifetime::Timeframe})); - - // A two-way broadcaster - lspec.emplace_back(defineBroadcaster("Broadcaster", - o2f::InputSpec{"input", "TST", "ToBC", 0, o2f::Lifetime::Timeframe}, - o2f::Outputs{{"TST", "BCAST0", 0, o2f::Lifetime::Timeframe}, - {"TST", "BCAST1", 0, o2f::Lifetime::Timeframe}})); - - // Two pipeline devices - lspec.emplace_back(definePipeline("pip0", o2f::InputSpec{"bc", "TST", "BCAST0", 0, o2f::Lifetime::Timeframe}, - o2f::OutputSpec{"TST", "PIP0", 0, o2f::Lifetime::Timeframe})); - lspec.emplace_back(definePipeline("pip1", o2f::InputSpec{"bc", "TST", "BCAST1", 0, o2f::Lifetime::Timeframe}, - o2f::OutputSpec{"TST", "PIP1", 0, o2f::Lifetime::Timeframe})); - - // A gatherer - lspec.emplace_back(defineMerger("Merger", o2f::Inputs{{"input1", "TST", "PIP0", 0, o2f::Lifetime::Timeframe}, {"input2", "TST", "PIP1", 0, o2f::Lifetime::Timeframe}}, - o2f::OutputSpec{"TST", "ToSink", 0, o2f::Lifetime::Timeframe})); - - // A sink which dumps messages - lspec.emplace_back(defineSink(o2f::InputSpec{"input", "TST", "ToSink", 0, o2f::Lifetime::Timeframe})); - return std::move(lspec); -} - -} // namespace o2::workflows 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_DPLBroadcasterMerger.cxx b/Framework/Utils/test/test_DPLBroadcasterMerger.cxx deleted file mode 100644 index 6ff554e75f462..0000000000000 --- a/Framework/Utils/test/test_DPLBroadcasterMerger.cxx +++ /dev/null @@ -1,31 +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 Gabriele Gaetano Fronzé, gfronze@cern.ch - -#include "Framework/DataRefUtils.h" -#include "Framework/ServiceRegistry.h" -#include "Framework/runDataProcessing.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/DataRef.h" -// FIXME: this should not be needed as the framework should be able to -// decode TClonesArray by itself. -#include "Framework/TMessageSerializer.h" -#include "DPLBroadcasterMerger.h" -#include "Framework/Logger.h" - -using namespace o2::framework; - -// This is how you can define your processing in a declarative way -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ - return o2::workflows::DPLBroadcasterMergerWorkflow(); -} diff --git a/Framework/Utils/test/test_DPLOutputTest.cxx b/Framework/Utils/test/test_DPLOutputTest.cxx deleted file mode 100644 index e49bea3074dd1..0000000000000 --- a/Framework/Utils/test/test_DPLOutputTest.cxx +++ /dev/null @@ -1,31 +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 Gabriele Gaetano Fronzé, gfronze@cern.ch - -#include "Framework/DataRefUtils.h" -#include "Framework/ServiceRegistry.h" -#include "Framework/runDataProcessing.h" -#include "Framework/ConfigParamRegistry.h" -#include "Framework/DataRef.h" -// FIXME: this should not be needed as the framework should be able to -// decode TClonesArray by itself. -#include "Framework/TMessageSerializer.h" -#include "DPLOutputTest.h" -#include "Framework/Logger.h" - -using namespace o2::framework; - -// This is how you can define your processing in a declarative way -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ - return o2::workflows::DPLOutputTest(); -} 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 b1a4b2107019c..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 @@ -52,16 +35,16 @@ if(ALIGPU_BUILD_TYPE STREQUAL "O2") COMPONENT_NAME GPU LABELS gpu) endif() - if (HIP_ENABLED) - o2_add_test(SMatrixImpHIP NAME test_SMatrixImpHIP - SOURCES test/testSMatrixImp.cu - HIPIFIED test - PUBLIC_LINK_LIBRARIES O2::${MODULE} - O2::MathUtils - ROOT::Core - COMPONENT_NAME GPU - LABELS gpu) - endif() +# if (HIP_ENABLED) +# o2_add_test(SMatrixImpHIP NAME test_SMatrixImpHIP +# SOURCES test/testSMatrixImp.cu +# HIPIFIED test +# PUBLIC_LINK_LIBRARIES O2::${MODULE} +# O2::MathUtils +# ROOT::Core +# COMPONENT_NAME GPU +# LABELS gpu) +# 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/GPU/GPUTracking/utils/opencl_compiler_structs.h b/GPU/Common/GPUCommonConfigurableParam.h similarity index 56% rename from GPU/GPUTracking/utils/opencl_compiler_structs.h rename to GPU/Common/GPUCommonConfigurableParam.h index 68e0a4f184480..475679df270e3 100644 --- a/GPU/GPUTracking/utils/opencl_compiler_structs.h +++ b/GPU/Common/GPUCommonConfigurableParam.h @@ -9,20 +9,34 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file opencl_compiler_structs.h +/// \file GPUCommonConfigurableParam.h /// \author David Rohr -struct _makefiles_opencl_platform_info { - char platform_profile[64]; - char platform_version[64]; - char platform_name[64]; - char platform_vendor[64]; - cl_uint count; -}; +#ifndef GPUCOMMONCONFIGURABLEPARAM_H +#define GPUCOMMONCONFIGURABLEPARAM_H + +#include "GPUCommonDef.h" -struct _makefiles_opencl_device_info { - char device_name[64]; - char device_vendor[64]; - cl_uint nbits; - size_t binary_size; +#if defined(GPUCA_STANDALONE) + +namespace o2::conf +{ +template +struct ConfigurableParamHelper { + static const T& Instance() + { + static T instance; + return instance; + } }; +#define O2ParamDef(...) +} // namespace o2::conf + +#else + +#include "CommonUtils/ConfigurableParam.h" +#include "CommonUtils/ConfigurableParamHelper.h" + +#endif + +#endif diff --git a/GPU/Common/GPUCommonLogger.h b/GPU/Common/GPUCommonLogger.h index 0b6b5ae401244..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 ---------- -#elif defined(GPUCA_STANDALONE) #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 372e067b14aff..8f81762d87373 100644 --- a/GPU/Common/GPUCommonMath.h +++ b/GPU/Common/GPUCommonMath.h @@ -74,6 +74,11 @@ class GPUCommonMath GPUhdni() constexpr static float Sqrt(float x); GPUd() static float InvSqrt(float x); template + GPUdi() constexpr static T Square(T x) + { + return x * x; + } + template GPUhd() constexpr static T Abs(T x); GPUd() constexpr static float ASin(float x); GPUd() constexpr static float ACos(float x); @@ -103,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); @@ -327,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 ffb17997b9190..14c388e450d73 100644 --- a/GPU/GPUTracking/Base/GPUConstantMem.h +++ b/GPU/GPUTracking/Base/GPUConstantMem.h @@ -17,13 +17,12 @@ #include "GPUTPCTracker.h" #include "GPUParam.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUErrors.h" #include "GPUTPCGMMerger.h" #include "GPUTRDTracker.h" -#include "GPUTPCConvert.h" #include "GPUTPCCompression.h" #include "GPUTPCDecompression.h" #include "GPUTPCClusterFinder.h" @@ -41,14 +40,13 @@ namespace o2::gpu { struct GPUConstantMem { GPUParam param; - GPUTPCTracker tpcTrackers[GPUCA_NSECTORS]; - GPUTPCConvert tpcConverter; + 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; @@ -57,9 +55,8 @@ 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/GPUGeneralKernels.h b/GPU/GPUTracking/Base/GPUGeneralKernels.h index eb816c91f5909..871cc21ee2bfa 100644 --- a/GPU/GPUTracking/Base/GPUGeneralKernels.h +++ b/GPU/GPUTracking/Base/GPUGeneralKernels.h @@ -16,7 +16,8 @@ #define GPUGENERALKERNELS_H #include "GPUDef.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" +#include "GPUDataTypesConfig.h" #if defined(GPUCA_GPUCODE) && !defined(GPUCA_GPUCODE_COMPILEKERNELS) && !defined(GPUCA_GPUCODE_HOSTONLY) #if defined(__CUDACC__) @@ -79,7 +80,7 @@ class GPUKernelTemplate }; typedef GPUconstantref() GPUConstantMem processorType; - GPUhdi() constexpr static GPUDataTypes::RecoStep GetRecoStep() { return GPUDataTypes::RecoStep::NoRecoStep; } + GPUhdi() constexpr static gpudatatypes::RecoStep GetRecoStep() { return gpudatatypes::RecoStep::NoRecoStep; } GPUhdi() static processorType* Processor(GPUConstantMem& processors) { return &processors; @@ -94,7 +95,7 @@ class GPUKernelTemplate class GPUMemClean16 : public GPUKernelTemplate { public: - GPUhdi() constexpr static GPUDataTypes::RecoStep GetRecoStep() { return GPUDataTypes::RecoStep::NoRecoStep; } + GPUhdi() constexpr static gpudatatypes::RecoStep GetRecoStep() { return gpudatatypes::RecoStep::NoRecoStep; } template GPUd() static void Thread(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUsharedref() GPUSharedMemory& smem, processorType& processors, GPUglobalref() void* ptr, uint64_t size); }; @@ -103,7 +104,7 @@ class GPUMemClean16 : public GPUKernelTemplate class GPUitoa : public GPUKernelTemplate { public: - GPUhdi() constexpr static GPUDataTypes::RecoStep GetRecoStep() { return GPUDataTypes::RecoStep::NoRecoStep; } + GPUhdi() constexpr static gpudatatypes::RecoStep GetRecoStep() { return gpudatatypes::RecoStep::NoRecoStep; } template GPUd() static void Thread(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUsharedref() GPUSharedMemory& smem, processorType& processors, GPUglobalref() int32_t* ptr, uint64_t size); }; diff --git a/GPU/GPUTracking/Base/GPUMemoryResource.cxx b/GPU/GPUTracking/Base/GPUMemoryResource.cxx index b22267859345a..aaf31837d04e3 100644 --- a/GPU/GPUTracking/Base/GPUMemoryResource.cxx +++ b/GPU/GPUTracking/Base/GPUMemoryResource.cxx @@ -13,4 +13,15 @@ /// \author David Rohr #include "GPUMemoryResource.h" +#include "GPUProcessor.h" using namespace o2::gpu; + +void* GPUMemoryResource::SetPointers(void* ptr) const +{ + return (mProcessor->*mSetPointers)(ptr); +} + +void* GPUMemoryResource::SetDevicePointers(void* ptr) const +{ + return (mProcessor->mLinkedProcessor->*mSetPointers)(ptr); +} diff --git a/GPU/GPUTracking/Base/GPUMemoryResource.h b/GPU/GPUTracking/Base/GPUMemoryResource.h index 947bcac504733..79960af9bc188 100644 --- a/GPU/GPUTracking/Base/GPUMemoryResource.h +++ b/GPU/GPUTracking/Base/GPUMemoryResource.h @@ -16,11 +16,14 @@ #define GPUMEMORYRESOURCE_H #include "GPUCommonDef.h" -#include "GPUProcessor.h" +#ifndef GPUCA_GPUCODE_DEVICE +#include +#endif namespace o2::gpu { +class GPUProcessor; struct GPUMemoryReuse { enum Type : int32_t { NONE = 0, @@ -80,13 +83,10 @@ class GPUMemoryResource } GPUMemoryResource(const GPUMemoryResource&) = default; - void* SetPointers(void* ptr) - { - return (mProcessor->*mSetPointers)(ptr); - } - void* SetDevicePointers(void* ptr) { return (mProcessor->mLinkedProcessor->*mSetPointers)(ptr); } - void* Ptr() { return mPtr; } - void* PtrDevice() { return mPtrDevice; } + void* SetPointers(void* ptr) const; + void* SetDevicePointers(void* ptr) const; + void* Ptr() const { return mPtr; } + void* PtrDevice() const { return mPtrDevice; } size_t Size() const { return mSize; } const char* Name() const { return mName; } MemoryType Type() const { return mType; } diff --git a/GPU/GPUTracking/Base/GPUParam.cxx b/GPU/GPUTracking/Base/GPUParam.cxx index 3062e1c4d2064..7edfa8ffd41d5 100644 --- a/GPU/GPUTracking/Base/GPUParam.cxx +++ b/GPU/GPUTracking/Base/GPUParam.cxx @@ -18,7 +18,7 @@ #include "GPUCommonMath.h" #include "GPUCommonConstants.h" #include "GPUTPCGMPolynomialFieldManager.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUConstantMem.h" #include "DetectorsBase/Propagator.h" #include "GPUTPCGeometry.h" @@ -35,9 +35,10 @@ void GPUParam::SetDefaults(float solenoidBz, bool assumeConstantBz) memset((void*)this, 0, sizeof(*this)); new (&rec) GPUSettingsRec; occupancyMap = nullptr; + 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}; @@ -86,32 +87,9 @@ 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; - par.earlyTpcTransform = false; } void GPUParam::UpdateSettings(const GPUSettingsGRP* g, const GPUSettingsProcessing* p, const GPURecoStepConfiguration* w, const GPUSettingsRecDynamic* d) @@ -122,13 +100,12 @@ void GPUParam::UpdateSettings(const GPUSettingsGRP* g, const GPUSettingsProcessi continuousMaxTimeBin = g->grpContinuousMaxTimeBin == -1 ? GPUSettings::TPC_MAX_TF_TIME_BIN : g->grpContinuousMaxTimeBin; tpcCutTimeBin = g->tpcCutTimeBin; } - par.earlyTpcTransform = rec.tpc.forceEarlyTransform == -1 ? (!par.continuousTracking) : rec.tpc.forceEarlyTransform; qptB5Scaler = CAMath::Abs(bzkG) > 0.1f ? CAMath::Abs(bzkG) / 5.006680f : 1.f; // Repeat here, since passing in g is optional if (p) { UpdateRun3ClusterErrors(p->param.tpcErrorParamY, p->param.tpcErrorParamZ); } if (w) { - par.dodEdx = dodEdxEnabled = w->steps.isSet(GPUDataTypes::RecoStep::TPCdEdx); + par.dodEdx = dodEdxEnabled = w->steps.isSet(gpudatatypes::RecoStep::TPCdEdx); if (dodEdxEnabled && p && p->tpcDownscaledEdx != 0) { dodEdxEnabled = (rand() % 100) < p->tpcDownscaledEdx; } @@ -156,16 +133,13 @@ void GPUParam::SetDefaults(const GPUSettingsGRP* g, const GPUSettingsRec* r, con SetDefaults(g->solenoidBzNominalGPU, g->constBz); if (r) { rec = *r; - if (rec.fitPropagateBzOnly == -1) { - rec.fitPropagateBzOnly = rec.tpc.nWays - 1; - } } UpdateSettings(g, p, w); } 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 2564fc9bf0462..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 @@ -63,11 +57,10 @@ struct GPUParam_t { GPUTPCGMPolynomialField polynomialField; // Polynomial approx. of magnetic field for TPC GM const uint32_t* occupancyMap; // Ptr to TPC occupancy map uint32_t occupancyTotal; // Total occupancy in the TPC (nCl / nHbf) - - GPUParamSector SectorParam[GPUCA_NSECTORS]; + uint32_t occupancyMapSize; // Size of occupancy map 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 @@ -79,20 +72,21 @@ struct GPUParam_t { struct GPUParam : public internal::GPUParam_t { #ifndef GPUCA_GPUCODE - void SetDefaults(float solenoidBz, bool assumeConstantBz); + void SetDefaults(float solenoidBz, bool assumeConstantBz = false); void SetDefaults(const GPUSettingsGRP* g, const GPUSettingsRec* r = nullptr, const GPUSettingsProcessing* p = nullptr, const GPURecoStepConfiguration* w = nullptr); void UpdateSettings(const GPUSettingsGRP* g, const GPUSettingsProcessing* p = nullptr, const GPURecoStepConfiguration* w = nullptr, const GPUSettingsRecDynamic* d = nullptr); - void UpdateBzOnly(float newSolenoidBz, bool assumeConstantBz); + void UpdateBzOnly(float newSolenoidBz, bool assumeConstantBz = false); 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 a118a8f639fe9..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 { @@ -213,7 +213,7 @@ GPUdi() float GPUParam::GetUnscaledMult(float time) const if (!occupancyMap) { return 0.f; } - const uint32_t bin = CAMath::Max(0.f, time / rec.tpc.occupancyMapTimeBins); + const uint32_t bin = CAMath::Min(occupancyMapSize - 1, CAMath::Max(0.f, time / rec.tpc.occupancyMapTimeBins)); return occupancyMap[bin]; } 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 09aae2aacf16d..7eda10cd31521 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.cxx +++ b/GPU/GPUTracking/Base/GPUReconstruction.cxx @@ -40,8 +40,9 @@ #include "GPULogging.h" #include "utils/strtag.h" +#include "utils/stdspinlock.h" -#ifdef GPUCA_O2_LIB +#ifndef GPUCA_STANDALONE #include "GPUO2InterfaceConfiguration.h" #endif @@ -62,7 +63,7 @@ struct GPUReconstructionPipelineQueue { } // namespace struct GPUReconstructionPipelineContext { - std::queue queue; + std::queue pipelineQueue; std::mutex mutex; std::condition_variable cond; bool terminate = false; @@ -262,26 +263,29 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() if (GetProcessingSettings().debugLevel > 0) { mProcessingSettings->recoTaskTiming = true; } + bool detMode = false; +#ifdef GPUCA_DETERMINISTIC_MODE + detMode = true; +#endif if (GetProcessingSettings().deterministicGPUReconstruction == -1) { - mProcessingSettings->deterministicGPUReconstruction = GetProcessingSettings().debugLevel >= 6; + 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 - mProcessingSettings->overrideClusterizerFragmentLen = TPC_MAX_FRAGMENT_LEN_GPU; - param().rec.tpc.nWaysOuter = true; - if (param().rec.tpc.looperInterpolationInExtraPass == -1) { - param().rec.tpc.looperInterpolationInExtraPass = 0; + 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); + } + mProcessingSettings->overrideClusterizerFragmentLen = TPC_MAX_FRAGMENT_LEN_GPU; if (GetProcessingSettings().createO2Output > 1) { mProcessingSettings->createO2Output = 1; } 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; @@ -292,20 +296,21 @@ int32_t GPUReconstruction::InitPhaseBeforeDevice() if (!GetProcessingSettings().createO2Output || !IsGPU()) { mProcessingSettings->clearO2OutputFromGPU = false; } - if (!(mRecoSteps.stepsGPUMask & GPUDataTypes::RecoStep::TPCMerging)) { + if (!(mRecoSteps.stepsGPUMask & gpudatatypes::RecoStep::TPCMerging)) { mProcessingSettings->mergerSortTracks = false; } - if (GetProcessingSettings().debugLevel > 3 || !IsGPU() || GetProcessingSettings().deterministicGPUReconstruction) { mProcessingSettings->delayedOutput = false; } - if (!GetProcessingSettings().rtc.enable) { mProcessingSettings->rtc.optConstexpr = false; } + if (GetProcessingSettings().allSanityChecks) { + mProcessingSettings->clusterizerZSSanityCheck = mProcessingSettings->mergerSanityCheck = mProcessingSettings->outputSanityCheck = true; + } + static_cast(*mMemoryScalers) = GetProcessingSettings().scaling; mMemoryScalers->scalingFactor = GetProcessingSettings().memoryScalingFactor; - mMemoryScalers->conservative = GetProcessingSettings().conservativeMemoryEstimate; mMemoryScalers->returnMaxVal = GetProcessingSettings().forceMaxMemScalers != 0; if (GetProcessingSettings().forceMaxMemScalers > 1) { mMemoryScalers->rescaleMaxMem(GetProcessingSettings().forceMaxMemScalers); @@ -338,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) { @@ -428,11 +433,11 @@ int32_t GPUReconstruction::InitPhaseAfterDevice() return 0; } -void GPUReconstruction::WriteConstantParams() +void GPUReconstruction::WriteConstantParams(int32_t stream) { if (IsGPU()) { const auto threadContext = GetThreadContext(); - WriteToConstantMemory(ptrDiff(&processors()->param, processors()), ¶m(), sizeof(param()), -1); + WriteToConstantMemory(ptrDiff(&processors()->param, processors()), ¶m(), sizeof(param()), stream); } } @@ -462,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; } } @@ -566,7 +571,7 @@ size_t GPUReconstruction::AllocateRegisteredPermanentMemory() return total; } -size_t GPUReconstruction::AllocateRegisteredMemoryHelper(GPUMemoryResource* res, void*& ptr, void*& memorypool, void* memorybase, size_t memorysize, void* (GPUMemoryResource::*setPtr)(void*), void*& memorypoolend, const char* device) +size_t GPUReconstruction::AllocateRegisteredMemoryHelper(GPUMemoryResource* res, void*& ptr, void*& memorypool, void* memorybase, size_t memorysize, void* (GPUMemoryResource::*setPtr)(void*) const, void*& memorypoolend, const char* device) { if (res->mReuse >= 0) { ptr = (&ptr == &res->mPtrDevice) ? mMemoryResources[res->mReuse].mPtrDevice : mMemoryResources[res->mReuse].mPtr; @@ -589,13 +594,14 @@ size_t GPUReconstruction::AllocateRegisteredMemoryHelper(GPUMemoryResource* res, throw std::bad_alloc(); } size_t retVal; + 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); @@ -607,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"; @@ -624,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) { @@ -634,17 +640,18 @@ 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 << "\n"; + std::cout << (res->mReuse >= 0 ? "Reused " : "Allocated ") << res->mName << ": " << res->mSize << " (individual" << ((res->mType & GPUMemoryResource::MEMORY_STACK) ? " stack" : "") << ")\n"; } if (res->mType & GPUMemoryResource::MEMORY_STACK) { + 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(); } @@ -654,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"; @@ -673,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(); } @@ -695,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(); } @@ -722,12 +729,13 @@ size_t GPUReconstruction::AllocateRegisteredMemory(int16_t ires, GPUOutputContro 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; } @@ -744,8 +752,8 @@ void* GPUReconstruction::AllocateDirectMemory(size_t size, int32_t type) void*& poolend = (type & GPUMemoryResource::MEMORY_GPU) ? mDeviceMemoryPoolEnd : mHostMemoryPoolEnd; char* retVal; if ((type & GPUMemoryResource::MEMORY_STACK)) { - poolend = (char*)poolend - size; - poolend = (char*)poolend - GPUProcessor::getAlignmentMod(poolend); + poolend = (char*)poolend - size; // TODO: Implement overflow check + poolend = (char*)poolend - GPUProcessor::getAlignmentMod(poolend); retVal = (char*)poolend; } else { GPUProcessor::computePointerWithAlignment(pool, retVal, size); @@ -756,13 +764,14 @@ void* GPUReconstruction::AllocateDirectMemory(size_t size, int32_t type) } UpdateMaxMemoryUsed(); if (GetProcessingSettings().allocDebugLevel >= 2) { - std::cout << "Allocated (unmanaged " << (type == GPUMemoryResource::MEMORY_GPU ? "gpu" : "host") << "): " << size << " - available: " << ptrDiff(poolend, pool) << "\n"; + std::cout << "Allocated (unmanaged " << ((type & GPUMemoryResource::MEMORY_GPU) ? "gpu" : "host") << "): " << size << " - available: " << ptrDiff(poolend, pool) << "\n"; } return retVal; } void* GPUReconstruction::AllocateVolatileDeviceMemory(size_t size) { + stdspinlock spinlock(mMemoryMutex); if (mVolatileMemoryStart == nullptr) { mVolatileMemoryStart = mDeviceMemoryPool; } @@ -787,8 +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]; - mVolatileChunks.emplace_back(retVal, alignedDeleter()); + char* retVal = alignedDefaultBufferAllocator(size); + stdspinlock spinlock(mMemoryMutex); + mVolatileChunks.emplace_back(retVal, alignedDefaultBufferDeleter()); return retVal; } @@ -866,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; @@ -877,8 +887,11 @@ void GPUReconstruction::PushNonPersistentMemory(uint64_t tag) mNonPersistentMemoryStack.emplace_back(mHostMemoryPoolEnd, mDeviceMemoryPoolEnd, mNonPersistentIndividualAllocations.size(), mNonPersistentIndividualDirectAllocations.size(), tag); } -void GPUReconstruction::PopNonPersistentMemory(RecoStep step, uint64_t tag) +void GPUReconstruction::PopNonPersistentMemory(RecoStep step, uint64_t tag, const GPUProcessor* proc) { + if (proc && GetProcessingSettings().memoryAllocationStrategy != GPUMemoryResource::ALLOCATION_INDIVIDUAL) { + GPUFatal("Processor-depending memory-free works only with allocation strategy ALLOCATION_INDIVIDUAL"); + } if (GetProcessingSettings().keepDisplayMemory || GetProcessingSettings().disableMemoryReuse) { return; } @@ -888,25 +901,34 @@ void GPUReconstruction::PopNonPersistentMemory(RecoStep step, uint64_t tag) if (tag != 0 && std::get<4>(mNonPersistentMemoryStack.back()) != tag) { GPUFatal("Tag mismatch when popping non persistent memory from stack : pop %s vs on stack %s", qTag2Str(tag).c_str(), qTag2Str(std::get<4>(mNonPersistentMemoryStack.back())).c_str()); } - if ((GetProcessingSettings().debugLevel >= 3 || GetProcessingSettings().allocDebugLevel) && (IsGPU() || GetProcessingSettings().forceHostMemoryPoolSize)) { - printf("Allocated memory after %30s (%8s) (Stack %zu): ", GPUDataTypes::RECO_STEP_NAMES[getRecoStepNum(step, true)], qTag2Str(std::get<4>(mNonPersistentMemoryStack.back())).c_str(), mNonPersistentMemoryStack.size()); + if (!proc && (GetProcessingSettings().debugLevel >= 3 || GetProcessingSettings().allocDebugLevel) && (IsGPU() || GetProcessingSettings().forceHostMemoryPoolSize)) { + printf("Allocated memory after %30s (%8s) (Stack %zu): ", gpudatatypes::RECO_STEP_NAMES[getRecoStepNum(step, true)], qTag2Str(std::get<4>(mNonPersistentMemoryStack.back())).c_str(), mNonPersistentMemoryStack.size()); PrintMemoryOverview(); printf("%76s", ""); PrintMemoryMax(); } - mHostMemoryPoolEnd = std::get<0>(mNonPersistentMemoryStack.back()); - mDeviceMemoryPoolEnd = std::get<1>(mNonPersistentMemoryStack.back()); for (uint32_t i = std::get<2>(mNonPersistentMemoryStack.back()); i < mNonPersistentIndividualAllocations.size(); i++) { GPUMemoryResource* res = mNonPersistentIndividualAllocations[i]; + if (proc && res->mProcessor != proc) { + continue; + } + if (GetProcessingSettings().allocDebugLevel >= 2 && (res->mPtr || res->mPtrDevice)) { + 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; } - mNonPersistentIndividualAllocations.resize(std::get<2>(mNonPersistentMemoryStack.back())); - mNonPersistentIndividualDirectAllocations.resize(std::get<3>(mNonPersistentMemoryStack.back())); - mNonPersistentMemoryStack.pop_back(); + if (!proc) { + stdspinlock spinlock(mMemoryMutex); + mHostMemoryPoolEnd = std::get<0>(mNonPersistentMemoryStack.back()); + mDeviceMemoryPoolEnd = std::get<1>(mNonPersistentMemoryStack.back()); + mNonPersistentIndividualAllocations.resize(std::get<2>(mNonPersistentMemoryStack.back())); + mNonPersistentIndividualDirectAllocations.resize(std::get<3>(mNonPersistentMemoryStack.back())); + mNonPersistentMemoryStack.pop_back(); + } } void GPUReconstruction::BlockStackedMemory(GPUReconstruction* rec) @@ -948,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 { @@ -999,7 +1021,7 @@ void GPUReconstruction::PrintMemoryStatistics() } printf("%59s CPU / %9s GPU\n", "", ""); for (auto it = sizes.begin(); it != sizes.end(); it++) { - printf("Allocation %30s %s: Size %'14zu / %'14zu\n", it->first.c_str(), it->second[2] ? "P" : " ", it->second[0], it->second[1]); + printf("Allocation %50s %s: Size %'14zu / %'14zu\n", it->first.c_str(), it->second[2] ? "P" : " ", it->second[0], it->second[1]); } PrintMemoryOverview(); for (uint32_t i = 0; i < mChains.size(); i++) { @@ -1052,8 +1074,8 @@ constexpr static inline int32_t getStepNum(T step, bool validCheck, int32_t N, c } // anonymous namespace } // namespace o2::gpu::internal -int32_t GPUReconstruction::getRecoStepNum(RecoStep step, bool validCheck) { return internal::getStepNum(step, validCheck, GPUDataTypes::N_RECO_STEPS, "Invalid Reco Step"); } -int32_t GPUReconstruction::getGeneralStepNum(GeneralStep step, bool validCheck) { return internal::getStepNum(step, validCheck, GPUDataTypes::N_GENERAL_STEPS, "Invalid General Step"); } +int32_t GPUReconstruction::getRecoStepNum(RecoStep step, bool validCheck) { return internal::getStepNum(step, validCheck, gpudatatypes::N_RECO_STEPS, "Invalid Reco Step"); } +int32_t GPUReconstruction::getGeneralStepNum(GeneralStep step, bool validCheck) { return internal::getStepNum(step, validCheck, gpudatatypes::N_GENERAL_STEPS, "Invalid General Step"); } void GPUReconstruction::RunPipelineWorker() { @@ -1067,13 +1089,13 @@ void GPUReconstruction::RunPipelineWorker() while (!terminate) { { std::unique_lock lk(mPipelineContext->mutex); - mPipelineContext->cond.wait(lk, [this] { return this->mPipelineContext->queue.size() > 0; }); + mPipelineContext->cond.wait(lk, [this] { return this->mPipelineContext->pipelineQueue.size() > 0; }); } GPUReconstructionPipelineQueue* q; { std::lock_guard lk(mPipelineContext->mutex); - q = mPipelineContext->queue.front(); - mPipelineContext->queue.pop(); + q = mPipelineContext->pipelineQueue.front(); + mPipelineContext->pipelineQueue.pop(); } if (q->op == 1) { terminate = 1; @@ -1110,26 +1132,23 @@ int32_t GPUReconstruction::EnqueuePipeline(bool terminate) if (rec->mPipelineContext->terminate) { throw std::runtime_error("Must not enqueue work after termination request"); } - rec->mPipelineContext->queue.push(q); + rec->mPipelineContext->pipelineQueue.push(q); rec->mPipelineContext->terminate = terminate; rec->mPipelineContext->cond.notify_one(); } q->c.wait(lkdone, [&q]() { return q->done; }); - if (q->retVal) { + if (terminate || (q->retVal && (q->retVal != 3 || !GetProcessingSettings().ignoreNonFatalGPUErrors))) { return q->retVal; } - if (terminate) { - return 0; - } else { - return mChains[0]->FinalizePipelinedProcessing(); - } + int32_t retVal2 = mChains[0]->FinalizePipelinedProcessing(); + return retVal2 ? retVal2 : q->retVal; } GPUChain* GPUReconstruction::GetNextChainInQueue() { GPUReconstruction* rec = mMaster ? mMaster : this; std::lock_guard lk(rec->mPipelineContext->mutex); - return rec->mPipelineContext->queue.size() && rec->mPipelineContext->queue.front()->op == 0 ? rec->mPipelineContext->queue.front()->chain : nullptr; + return rec->mPipelineContext->pipelineQueue.size() && rec->mPipelineContext->pipelineQueue.front()->op == 0 ? rec->mPipelineContext->pipelineQueue.front()->chain : nullptr; } void GPUReconstruction::PrepareEvent() // TODO: Clean this up, this should not be called from chainTracking but before @@ -1203,7 +1222,7 @@ void GPUReconstruction::UpdateSettings(const GPUSettingsGRP* g, const GPUSetting mProcessingSettings->resetTimers = p->resetTimers; } GPURecoStepConfiguration* w = nullptr; - if (mRecoSteps.steps.isSet(GPUDataTypes::RecoStep::TPCdEdx)) { + if (mRecoSteps.steps.isSet(gpudatatypes::RecoStep::TPCdEdx)) { w = &mRecoSteps; } param().UpdateSettings(g, p, w, d); @@ -1218,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()); @@ -1230,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 420e602e61352..4479eb696808e 100644 --- a/GPU/GPUTracking/Base/GPUReconstruction.h +++ b/GPU/GPUTracking/Base/GPUReconstruction.h @@ -25,14 +25,15 @@ #include #include #include +#include -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUMemoryResource.h" #include "GPUOutputControl.h" - -/*#include "GPUParam.h" -#include "GPUSettings.h" -#include "GPULogging.h"*/ +#include "GPUParam.h" +#include "GPUConstantMem.h" +#include "GPUCommonAlignedAlloc.h" +#include "GPUDef.h" namespace o2::its { @@ -81,20 +82,20 @@ 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; - using RecoStep = GPUDataTypes::RecoStep; - using GeneralStep = GPUDataTypes::GeneralStep; - using RecoStepField = GPUDataTypes::RecoStepField; - using InOutTypeField = GPUDataTypes::InOutTypeField; + using GeometryType = gpudatatypes::GeometryType; + using DeviceType = gpudatatypes::DeviceType; + using RecoStep = gpudatatypes::RecoStep; + using GeneralStep = gpudatatypes::GeneralStep; + using RecoStepField = gpudatatypes::RecoStepField; + 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); @@ -179,7 +180,7 @@ class GPUReconstruction void ReturnVolatileMemory(); ThrustVolatileAllocator getThrustVolatileDeviceAllocator(); void PushNonPersistentMemory(uint64_t tag); - void PopNonPersistentMemory(RecoStep step, uint64_t tag); + void PopNonPersistentMemory(RecoStep step, uint64_t tag, const GPUProcessor* proc = nullptr); void BlockStackedMemory(GPUReconstruction* rec); void UnblockStackedMemory(); void ResetRegisteredMemoryPointers(GPUProcessor* proc); @@ -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); @@ -260,7 +268,7 @@ class GPUReconstruction virtual int32_t InitDevice() = 0; int32_t InitPhasePermanentMemory(); int32_t InitPhaseAfterDevice(); - void WriteConstantParams(); + void WriteConstantParams(int32_t stream = -1); virtual int32_t ExitDevice() = 0; virtual size_t WriteToConstantMemory(size_t offset, const void* src, size_t size, int32_t stream = -1, gpu_reconstruction_kernels::deviceEvent* ev = nullptr) = 0; void UpdateMaxMemoryUsed(); @@ -279,7 +287,7 @@ class GPUReconstruction static std::string getBackendVersions(); // Private helper functions for memory management - size_t AllocateRegisteredMemoryHelper(GPUMemoryResource* res, void*& ptr, void*& memorypool, void* memorybase, size_t memorysize, void* (GPUMemoryResource::*SetPointers)(void*), void*& memorypoolend, const char* device); + size_t AllocateRegisteredMemoryHelper(GPUMemoryResource* res, void*& ptr, void*& memorypool, void* memorybase, size_t memorysize, void* (GPUMemoryResource::*SetPointers)(void*) const, void*& memorypoolend, const char* device); size_t AllocateRegisteredPermanentMemory(); // Private helper functions for reading / writing / allocating IO buffer from/to file @@ -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,13 @@ 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 f7b08f9dd0c48..9fbe9e1171af3 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionCPU.cxx @@ -17,7 +17,6 @@ #include "GPUReconstructionThreading.h" #include "GPUChain.h" #include "GPUDefParametersRuntime.h" -#include "GPUTPCClusterData.h" #include "GPUTPCGMMergedTrack.h" #include "GPUTPCGMMergedTrackHit.h" #include "GPUTRDTrackletWord.h" @@ -32,9 +31,11 @@ #include "GPULogging.h" #include "GPUMemorySizeScalers.h" #include "GPUReconstructionProcessingKernels.inc" +#include "GPUTPCClusterOccupancyMap.h" #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,28 +231,27 @@ 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; if (GetProcessingSettings().doublePipeline) { - int32_t retVal = EnqueuePipeline(); - if (retVal) { - return retVal; - } + retVal = EnqueuePipeline(); } else { if (mSlaves.size() || mMaster) { WriteConstantParams(); // Reinitialize // TODO: Get this in sync with GPUChainTracking::DoQueuedUpdates, and consider the doublePipeline } for (uint32_t i = 0; i < mChains.size(); i++) { - int32_t retVal = mChains[i]->RunChain(); - if (retVal) { - return retVal; - } - } - if (GetProcessingSettings().tpcFreeAllocatedMemoryAfterProcessing) { - ClearAllocatedMemory(); + retVal = mChains[i]->RunChain(); } } + if (retVal != 0 && retVal != 2) { + return retVal; + } mTimerTotal.Stop(); + if (GetProcessingSettings().tpcFreeAllocatedMemoryAfterProcessing) { + ClearAllocatedMemory(); + } mStatCPUTime += (double)(std::clock() - cpuTimerStart) / CLOCKS_PER_SEC; if (GetProcessingSettings().debugLevel >= 3 || GetProcessingSettings().allocDebugLevel) { GPUInfo("Allocated memory when ending processing %36s", ""); @@ -264,9 +264,12 @@ int32_t GPUReconstructionCPU::RunChains() nEventReport += " (avergage of " + std::to_string(mStatNEvents) + " runs)"; } double kernelTotal = 0; - std::vector kernelStepTimes(GPUDataTypes::N_RECO_STEPS, 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) { @@ -286,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,18 +297,15 @@ int32_t GPUReconstructionCPU::RunChains() } } if (GetProcessingSettings().recoTaskTiming) { - for (int32_t i = 0; i < GPUDataTypes::N_RECO_STEPS; i++) { + 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,16 +317,13 @@ int32_t GPUReconstructionCPU::RunChains() mTimersRecoSteps[i].countToHost = 0; } } - for (int32_t i = 0; i < GPUDataTypes::N_GENERAL_STEPS; i++) { + 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()); } @@ -340,7 +333,13 @@ int32_t GPUReconstructionCPU::RunChains() mTimerTotal.Reset(); } - return 0; + if (GetProcessingSettings().memoryStat) { + PrintMemoryStatistics(); + } else if (GetProcessingSettings().debugLevel >= 2) { + PrintMemoryOverview(); + } + + return retVal; } void GPUReconstructionCPU::ResetDeviceProcessorTypes() @@ -352,17 +351,25 @@ void GPUReconstructionCPU::ResetDeviceProcessorTypes() } } -void GPUReconstructionCPU::UpdateParamOccupancyMap(const uint32_t* mapHost, const uint32_t* mapGPU, uint32_t occupancyTotal, int32_t stream) +void GPUReconstructionCPU::UpdateParamOccupancyMap(const uint32_t* mapHost, const uint32_t* mapGPU, uint32_t occupancyTotal, uint32_t mapSize, int32_t stream, deviceEvent* ev) { + if (mapHost && mapSize != GPUTPCClusterOccupancyMapBin::getNBins(param())) { + throw std::runtime_error("Updating occupancy map with object of invalid size"); + } param().occupancyMap = mapHost; + param().occupancyMapSize = mapSize; param().occupancyTotal = occupancyTotal; if (IsGPU()) { - if (!((size_t)¶m().occupancyTotal - (size_t)¶m().occupancyMap == sizeof(param().occupancyMap) && sizeof(param().occupancyMap) == sizeof(size_t) && sizeof(param().occupancyTotal) < sizeof(size_t))) { + if (!((size_t)¶m().occupancyMapSize - (size_t)¶m().occupancyMap == sizeof(param().occupancyMap) + sizeof(param().occupancyTotal) && sizeof(param().occupancyMap) == sizeof(void*) && sizeof(param().occupancyTotal) == sizeof(uint32_t))) { // TODO: Make static assert, and check alignment throw std::runtime_error("occupancy data not consecutive in GPUParam"); } + struct tmpOccuapncyParam { + const void* ptr; + uint32_t total; + uint32_t size; + }; + tmpOccuapncyParam tmp = {mapGPU, occupancyTotal, mapSize}; const auto holdContext = GetThreadContext(); - size_t tmp[2] = {(size_t)mapGPU, 0}; - memcpy(&tmp[1], &occupancyTotal, sizeof(occupancyTotal)); - WriteToConstantMemory((char*)&processors()->param.occupancyMap - (char*)processors(), &tmp, sizeof(param().occupancyMap) + sizeof(param().occupancyTotal), stream); + WriteToConstantMemory((char*)&processors()->param.occupancyMap - (char*)processors(), &tmp, sizeof(tmp), stream, ev); } } diff --git a/GPU/GPUTracking/Base/GPUReconstructionCPU.h b/GPU/GPUTracking/Base/GPUReconstructionCPU.h index 768c301f24327..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 @@ -48,7 +50,7 @@ class GPUReconstructionCPU : public GPUReconstructionProcessing::KernelInterface int32_t RunChains() override; - void UpdateParamOccupancyMap(const uint32_t* mapHost, const uint32_t* mapGPU, uint32_t occupancyTotal, int32_t stream = -1); + void UpdateParamOccupancyMap(const uint32_t* mapHost, const uint32_t* mapGPU, uint32_t occupancyTotal, uint32_t mapSize, int32_t stream = -1, deviceEvent* ev = nullptr); protected: struct GPUProcessorProcessors : public GPUProcessor { @@ -88,8 +90,6 @@ class GPUReconstructionCPU : public GPUReconstructionProcessing::KernelInterface int32_t ExitDevice() override; int32_t GetThread(); - virtual int32_t DoStuckProtection(int32_t stream, deviceEvent event) { return 0; } - // Pointers to tracker classes GPUProcessorProcessors mProcShadow; // Host copy of tracker objects that will be used on the GPU GPUConstantMem*& mProcessorsShadow = mProcShadow.mProcessorsProc; @@ -102,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 7bf819a74e1b6..7970fbe753ec8 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h +++ b/GPU/GPUTracking/Base/GPUReconstructionCPUKernels.h @@ -26,8 +26,8 @@ template inline void GPUReconstructionCPU::runKernelInterface(krnlSetup&& setup, Args const&... args) { HighResTimer* t = nullptr; - GPUDataTypes::RecoStep myStep = S::GetRecoStep() == GPUDataTypes::RecoStep::NoRecoStep ? setup.x.step : S::GetRecoStep(); - if (myStep == GPUDataTypes::RecoStep::NoRecoStep) { + gpudatatypes::RecoStep myStep = S::GetRecoStep() == gpudatatypes::RecoStep::NoRecoStep ? setup.x.step : S::GetRecoStep(); + if (myStep == gpudatatypes::RecoStep::NoRecoStep) { throw std::runtime_error("Failure running general kernel without defining RecoStep"); } int32_t cpuFallback = IsGPU() ? (setup.x.device == krnlDeviceType::CPU ? 2 : (mRecoSteps.stepsGPUMask & myStep) != myStep) : 0; @@ -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 2dec88393f632..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,12 +20,12 @@ #endif #include "GPUReconstructionConvert.h" -#include "TPCFastTransform.h" +#include "TPCFastTransformPOD.h" #include "GPUTPCClusterData.h" #include "GPUO2DataTypes.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUTPCGeometry.h" -#include "AliHLTTPCRawCluster.h" // TODO: Is this still needed at all, or can it be removed? +#include "AliHLTTPCRawCluster.h" // VS: It can not be removed. Used in line 93. #include "GPUParam.h" #include "GPULogging.h" #include @@ -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/GPUReconstructionIncludes.h b/GPU/GPUTracking/Base/GPUReconstructionIncludes.h index d3f11d86a731d..4c057521fe6e7 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionIncludes.h +++ b/GPU/GPUTracking/Base/GPUReconstructionIncludes.h @@ -24,7 +24,7 @@ #include "GPUDef.h" #include "GPULogging.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include #include diff --git a/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h b/GPU/GPUTracking/Base/GPUReconstructionIncludesITS.h index c4202e9980d24..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/GPUReconstructionKernelMacros.h b/GPU/GPUTracking/Base/GPUReconstructionKernelMacros.h index a03d9de13ef8f..cc1c62bed507d 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionKernelMacros.h +++ b/GPU/GPUTracking/Base/GPUReconstructionKernelMacros.h @@ -32,6 +32,21 @@ #define GPUCA_M_KRNL_NAME(...) GPUCA_M_KRNL_NAME_A(GPUCA_M_STRIP(__VA_ARGS__)) #if defined(GPUCA_GPUCODE) || defined(GPUCA_GPUCODE_HOSTONLY) + +#if defined(__HIPCC__) && !defined(GPUCA_GPUCODE_NO_LAUNCH_BOUNDS) + static_assert(GPUCA_PAR_AMD_EUS_PER_CU > 0); + #define GPUCA_MIN_WARPS_PER_EU(maxThreadsPerBlock, minBlocksPerCU) GPUCA_CEIL_INT_DIV((minBlocksPerCU) * (maxThreadsPerBlock), (GPUCA_WARP_SIZE * GPUCA_PAR_AMD_EUS_PER_CU)) + + #define GPUCA_LB_ARGS_1(maxThreadsPerBlock) maxThreadsPerBlock + #define GPUCA_LB_ARGS_2(maxThreadsPerBlock, minBlocksPerCU) maxThreadsPerBlock, GPUCA_MIN_WARPS_PER_EU(maxThreadsPerBlock, minBlocksPerCU) + + #define GPUCA_LAUNCH_BOUNDS_SELECT(n, ...) GPUCA_M_CAT(GPUCA_LB_ARGS_, n)(__VA_ARGS__) + #define GPUCA_LAUNCH_BOUNDS_DISP(...) GPUCA_LAUNCH_BOUNDS_SELECT(GPUCA_M_COUNT(__VA_ARGS__), __VA_ARGS__) + #define GPUCA_KRNL_REG_DEFAULT(args) __launch_bounds__(GPUCA_LAUNCH_BOUNDS_DISP(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args)))) +#elif !defined(GPUCA_GPUCODE_NO_LAUNCH_BOUNDS) + #define GPUCA_KRNL_REG_DEFAULT(args) __launch_bounds__(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args))) +#endif + #ifndef GPUCA_KRNL_REG #define GPUCA_KRNL_REG(...) #endif diff --git a/GPU/GPUTracking/Base/GPUReconstructionLibrary.cxx b/GPU/GPUTracking/Base/GPUReconstructionLibrary.cxx index c70c5d8c51d6f..2e22d4c07e77e 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionLibrary.cxx +++ b/GPU/GPUTracking/Base/GPUReconstructionLibrary.cxx @@ -62,15 +62,15 @@ GPUReconstruction* GPUReconstruction::CreateInstance(const GPUSettingsDeviceBack if (retVal == nullptr) { if (cfg.forceDeviceType) { - GPUError("Error: Could not load GPUReconstruction for specified device: %s (%u)", GPUDataTypes::DEVICE_TYPE_NAMES[type], cfg.deviceType); + GPUError("Error: Could not load GPUReconstruction for specified device: %s (%u)", gpudatatypes::DEVICE_TYPE_NAMES[type], cfg.deviceType); } else if (type != DeviceType::CPU) { - GPUError("Could not load GPUReconstruction for device type %s (%u), falling back to CPU version", GPUDataTypes::DEVICE_TYPE_NAMES[type], cfg.deviceType); + GPUError("Could not load GPUReconstruction for device type %s (%u), falling back to CPU version", gpudatatypes::DEVICE_TYPE_NAMES[type], cfg.deviceType); GPUSettingsDeviceBackend cfg2 = cfg; cfg2.deviceType = DeviceType::CPU; retVal = CreateInstance(cfg2); } } else { - GPUInfo("Created GPUReconstruction instance for device type %s (%u)%s", GPUDataTypes::DEVICE_TYPE_NAMES[type], cfg.deviceType, cfg.master ? " (slave)" : ""); + GPUInfo("Created GPUReconstruction instance for device type %s (%u)%s", gpudatatypes::DEVICE_TYPE_NAMES[type], cfg.deviceType, cfg.master ? " (slave)" : ""); } return retVal; @@ -107,14 +107,14 @@ std::shared_ptr* GPUReconstruction::GetLibrary return nullptr; } if (verbose) { - GPUInfo("%s Support not compiled in for device type %u (%s)", GPUDataTypes::DEVICE_TYPE_NAMES[type], (uint32_t)type, GPUDataTypes::DEVICE_TYPE_NAMES[type]); + GPUInfo("%s Support not compiled in for device type %u (%s)", gpudatatypes::DEVICE_TYPE_NAMES[type], (uint32_t)type, gpudatatypes::DEVICE_TYPE_NAMES[type]); } return nullptr; } GPUReconstruction* GPUReconstruction::CreateInstance(const char* type, bool forceType, GPUReconstruction* master) { - DeviceType t = GPUDataTypes::GetDeviceType(type); + DeviceType t = gpudatatypes::GetDeviceType(type); if (t == DeviceType::INVALID_DEVICE) { GPUError("Invalid device type: %s", type); return nullptr; diff --git a/GPU/GPUTracking/Base/GPUReconstructionProcessing.h b/GPU/GPUTracking/Base/GPUReconstructionProcessing.h index 9e611e57148c6..3d9507e48e292 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionProcessing.h +++ b/GPU/GPUTracking/Base/GPUReconstructionProcessing.h @@ -92,14 +92,14 @@ class GPUReconstructionProcessing : public GPUReconstruction }; struct krnlExec { - constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, GPUReconstruction::krnlDeviceType d = GPUReconstruction::krnlDeviceType::Auto) : nBlocks(b), nThreads(t), stream(s), device(d), step(GPUDataTypes::RecoStep::NoRecoStep) {} - constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, GPUDataTypes::RecoStep st) : nBlocks(b), nThreads(t), stream(s), device(GPUReconstruction::krnlDeviceType::Auto), step(st) {} - constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, GPUReconstruction::krnlDeviceType d, GPUDataTypes::RecoStep st) : nBlocks(b), nThreads(t), stream(s), device(d), step(st) {} + constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, GPUReconstruction::krnlDeviceType d = GPUReconstruction::krnlDeviceType::Auto) : nBlocks(b), nThreads(t), stream(s), device(d), step(gpudatatypes::RecoStep::NoRecoStep) {} + constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, gpudatatypes::RecoStep st) : nBlocks(b), nThreads(t), stream(s), device(GPUReconstruction::krnlDeviceType::Auto), step(st) {} + constexpr krnlExec(uint32_t b, uint32_t t, int32_t s, GPUReconstruction::krnlDeviceType d, gpudatatypes::RecoStep st) : nBlocks(b), nThreads(t), stream(s), device(d), step(st) {} uint32_t nBlocks; uint32_t nThreads; int32_t stream; GPUReconstruction::krnlDeviceType device; - GPUDataTypes::RecoStep step; + gpudatatypes::RecoStep step; }; struct krnlRunRange { constexpr krnlRunRange() = default; @@ -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)]; } @@ -198,10 +199,10 @@ class GPUReconstructionProcessing : public GPUReconstruction size_t memSize; // Memory size for memory bandwidth computation }; - HighResTimer mTimersGeneralSteps[GPUDataTypes::N_GENERAL_STEPS]; + HighResTimer mTimersGeneralSteps[gpudatatypes::N_GENERAL_STEPS]; std::vector> mTimers; - RecoStepTimerMeta mTimersRecoSteps[GPUDataTypes::N_RECO_STEPS]; + RecoStepTimerMeta mTimersRecoSteps[gpudatatypes::N_RECO_STEPS]; HighResTimer mTimerTotal; template HighResTimer& getKernelTimer(RecoStep step, int32_t num = 0, size_t addMemorySize = 0, bool increment = true); @@ -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/GPUReconstructionTimeframe.h b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.h index 47cbfa0a1a5b6..14fc949240a7f 100644 --- a/GPU/GPUTracking/Base/GPUReconstructionTimeframe.h +++ b/GPU/GPUTracking/Base/GPUReconstructionTimeframe.h @@ -16,7 +16,7 @@ #define GPURECONSTRUCTIONTIMEFRAME_H #include "GPUChainTracking.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUTPCGeometry.h" #include #include diff --git a/GPU/GPUTracking/Base/cuda/CMakeLists.txt b/GPU/GPUTracking/Base/cuda/CMakeLists.txt index 7f595b28a582a..6e54187332c9b 100644 --- a/GPU/GPUTracking/Base/cuda/CMakeLists.txt +++ b/GPU/GPUTracking/Base/cuda/CMakeLists.txt @@ -41,9 +41,11 @@ set(GPU_RTC_FLAGS "${CMAKE_CUDA_FLAGS} ${CMAKE_CUDA_FLAGS_${CMAKE_BUILD_TYPE_UPP set(GPU_RTC_FLAGS_ARCH "") if(CUDA_COMPUTETARGET) foreach(CUDA_ARCH ${CUDA_COMPUTETARGET}) - set(GPU_RTC_FLAGS_ARCH "${GPU_RTC_FLAGS_ARCH} -gencode arch=compute_${CUDA_ARCH},code=sm_${CUDA_ARCH}") + string(REGEX REPLACE "-.*$" "" CUDA_ARCH_STRIPPED "${CUDA_ARCH}") + set(GPU_RTC_FLAGS_ARCH "${GPU_RTC_FLAGS_ARCH} -gencode arch=compute_${CUDA_ARCH_STRIPPED},code=sm_${CUDA_ARCH_STRIPPED}") endforeach() list (GET CUDA_COMPUTETARGET 0 RTC_CUDA_ARCH) + string(REGEX REPLACE "-.*$" "" RTC_CUDA_ARCH "${RTC_CUDA_ARCH}") set(RTC_CUDA_ARCH "${RTC_CUDA_ARCH}0") else() set(RTC_CUDA_ARCH "750") @@ -146,8 +148,8 @@ if (onnxruntime_FOUND) endif() # Setting target architecture and adding GPU libraries -target_link_libraries(${targetName} PRIVATE cuda cudart nvrtc) -set_target_cuda_arch(${targetName}) +target_link_libraries(${targetName} PRIVATE cuda cudart) +set_target_gpu_arch("CUDA" ${targetName}) #target_link_options(${targetName} PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version_script.ld") #set_target_properties(${targetName} PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/version_script.ld) diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu index 71582b4fed55e..040a4b84a0f64 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDA.cu @@ -94,13 +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 + vertexerTraits->reset(new o2::its::VertexerTraits<7>); } if (timeFrame) { - timeFrame->reset(new o2::its::gpu::TimeFrameGPU); + timeFrame->reset(new o2::its::gpu::TimeFrameGPU<7>); } } @@ -111,13 +111,14 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() constexpr int32_t reqVerMin = 0; #endif if (GetProcessingSettings().rtc.enable && GetProcessingSettings().rtctech.runTest == 2) { - mWarpSize = GPUCA_WARP_SIZE; + mWarpSize = GetProcessingSettings().rtc.overrideWarpSize != -1 ? GetProcessingSettings().rtc.overrideWarpSize : GPUCA_WARP_SIZE; genAndLoadRTC(); exit(0); } if (mMaster == nullptr) { cudaDeviceProp deviceProp; + int deviceMemoryClockRate{0}, deviceClockRate{0}; int32_t count, bestDevice = -1; double bestDeviceSpeed = -1, deviceSpeed; if (GPUChkErrI(cudaGetDeviceCount(&count))) { @@ -151,7 +152,9 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() if (GetProcessingSettings().debugLevel >= 4) { GPUInfo("Obtained current memory usage for device %d", i); } - if (GPUChkErrI(cudaGetDeviceProperties(&deviceProp, i))) { + if (GPUChkErrI(cudaGetDeviceProperties(&deviceProp, i)) || + GPUChkErrI(cudaDeviceGetAttribute(&deviceMemoryClockRate, cudaDevAttrMemoryClockRate, i)) || + GPUChkErrI(cudaDeviceGetAttribute(&deviceClockRate, cudaDevAttrClockRate, i))) { continue; } if (GetProcessingSettings().debugLevel >= 4) { @@ -170,7 +173,7 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() deviceFailure = "Insufficient GPU memory"; } - deviceSpeed = (double)deviceProp.multiProcessorCount * (double)deviceProp.clockRate * (double)deviceProp.warpSize * (double)free * (double)deviceProp.major * (double)deviceProp.major; + deviceSpeed = (double)deviceProp.multiProcessorCount * (double)deviceClockRate * (double)deviceProp.warpSize * (double)free * (double)deviceProp.major * (double)deviceProp.major; if (GetProcessingSettings().debugLevel >= 2) { GPUImportant("Device %s%2d: %s (Rev: %d.%d - Mem Avail %lu / %lu)%s %s", deviceOK ? " " : "[", i, deviceProp.name, deviceProp.major, deviceProp.minor, free, (size_t)deviceProp.totalGlobalMem, deviceOK ? " " : " ]", deviceOK ? "" : deviceFailure); } @@ -184,7 +187,7 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() bestDeviceSpeed = deviceSpeed; } else { if (GetProcessingSettings().debugLevel >= 2 && GetProcessingSettings().deviceNum < 0) { - GPUInfo("Skipping: Speed %f < %f\n", deviceSpeed, bestDeviceSpeed); + GPUInfo("Skipping: Speed %f <= %f\n", deviceSpeed, bestDeviceSpeed); } } } @@ -237,13 +240,13 @@ int32_t GPUReconstructionCUDA::InitDevice_Runtime() GPUInfo("\ttotalConstMem = %ld", (uint64_t)deviceProp.totalConstMem); GPUInfo("\tmajor = %d", deviceProp.major); GPUInfo("\tminor = %d", deviceProp.minor); - GPUInfo("\tclockRate = %d", deviceProp.clockRate); - GPUInfo("\tmemoryClockRate = %d", deviceProp.memoryClockRate); + GPUInfo("\tclockRate = %d", deviceClockRate); + GPUInfo("\tdeviceMemoryClockRateRate = %d", deviceMemoryClockRate); GPUInfo("\tmultiProcessorCount = %d", deviceProp.multiProcessorCount); GPUInfo("\ttextureAlignment = %ld", (uint64_t)deviceProp.textureAlignment); GPUInfo(" "); } - if (deviceProp.warpSize != GPUCA_WARP_SIZE && !GetProcessingSettings().rtc.enable) { + if (GetProcessingSettings().rtc.enable ? (GetProcessingSettings().rtc.overrideWarpSize != -1 && deviceProp.warpSize != GetProcessingSettings().rtc.overrideWarpSize) : (deviceProp.warpSize != GPUCA_WARP_SIZE)) { throw std::runtime_error("Invalid warp size on GPU"); } mWarpSize = deviceProp.warpSize; @@ -265,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); @@ -369,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, deviceProp.clockRate, 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 8c3fb92c11c9e..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; @@ -74,10 +76,25 @@ int32_t GPUReconstructionCUDA::genRTC(std::string& filename, uint32_t& nCompile) } fclose(fp); } - const std::string launchBounds = o2::gpu::internal::GPUDefParametersExport(*mParDevice, true) + + if constexpr (std::string_view("CUDA") == "HIP") { // Check if we are RTC-compiling for HIP + if (GetProcessingSettings().hipOverrideAMDEUSperCU > 0) { + mParDevice->par_AMD_EUS_PER_CU = GetProcessingSettings().hipOverrideAMDEUSperCU; + } else if (mParDevice->par_AMD_EUS_PER_CU <= 0) { + 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/cuda/GPUReconstructionCUDAKernels.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAKernels.cu index e6ed94bba2cec..d668bc7da1513 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAKernels.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAKernels.cu @@ -12,6 +12,7 @@ /// \file GPUReconstructionCUDAKernels.cu /// \author David Rohr +#define GPUCA_COMPILEKERNELS #include "GPUReconstructionCUDAIncludesSystem.h" #include "GPUReconstructionCUDADef.h" @@ -74,7 +75,7 @@ inline void GPUReconstructionCUDA::runKernelBackend(const krnlSetupTime& _xyz, c } #undef GPUCA_KRNL_REG -#define GPUCA_KRNL_REG(args) __launch_bounds__(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args))) +#define GPUCA_KRNL_REG(...) GPUCA_KRNL_REG_DEFAULT(__VA_ARGS__) // clang-format off #if defined(GPUCA_KERNEL_COMPILE_MODE) && GPUCA_KERNEL_COMPILE_MODE != 1 // ---------- COMPILE_MODE = perkernel ---------- diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDARTCCalls.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDARTCCalls.cu index 3e4d3113fb995..571428dc39e21 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDARTCCalls.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDARTCCalls.cu @@ -15,7 +15,7 @@ #define GPUCA_GPUCODE_HOSTONLY #define GPUCA_GPUCODE_NO_LAUNCH_BOUNDS -#define GPUCA_KRNL_REG(args) __launch_bounds__(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args))) +#define GPUCA_KRNL_REG(args) __launch_bounds__(GPUCA_M_STRIP(args)) #include "GPUReconstructionCUDAIncludesSystem.h" #include "GPUReconstructionCUDADef.h" diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu index 847011a70f7f9..82759aab48d70 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDAkernel.template.cu @@ -14,7 +14,7 @@ #define GPUCA_GPUCODE_COMPILEKERNELS #include "GPUReconstructionCUDAIncludesSystem.h" -#define GPUCA_KRNL_REG(args) __launch_bounds__(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args))) +#define GPUCA_KRNL_REG(...) GPUCA_KRNL_REG_DEFAULT(__VA_ARGS__) #define GPUCA_KRNL(...) GPUCA_KRNLGPU(__VA_ARGS__); #include "GPUReconstructionCUDADef.h" #include "GPUReconstructionKernelMacros.h" diff --git a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDArtc.cu b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDArtc.cu index 66c02d6ed251c..32286905f2a71 100644 --- a/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDArtc.cu +++ b/GPU/GPUTracking/Base/cuda/GPUReconstructionCUDArtc.cu @@ -20,7 +20,7 @@ #define GPUCA_DETERMINISTIC_CODE(...) GPUCA_DETERMINISTIC_CODE(__VA_ARGS__) #define GPUCA_RTC_CONSTEXPR GPUCA_RTC_CONSTEXPR -// GPUReconstructionCUDAIncludesSystem.h prependended by CMakewithout preprocessor running +// GPUReconstructionCUDAIncludesSystem.h prependended by CMake without preprocessor running #include "GPUReconstructionCUDADef.h" #include "GPUReconstructionIncludesDeviceAll.h" diff --git a/GPU/GPUTracking/Base/hip/CMakeLists.txt b/GPU/GPUTracking/Base/hip/CMakeLists.txt index 17bbf46795761..50d710fd9d557 100644 --- a/GPU/GPUTracking/Base/hip/CMakeLists.txt +++ b/GPU/GPUTracking/Base/hip/CMakeLists.txt @@ -11,6 +11,12 @@ set(MODULE GPUTrackingHIP) +if(GPUCA_BUILD_DEBUG AND GPUCA_BUILD_DEBUG_HOSTONLY) + set(CMAKE_BUILD_TYPE RELEASE) + set(CMAKE_HIP_FLAGS_RELEASE "${CMAKE_HIP_FLAGS_RELEASE} -O3 -march=native -ggdb -fno-sanitize=all -DNDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -march=native -ggdb -fno-sanitize=all -DNDEBUG") +endif() + # -------------------------------- Options ------------------------------------------------------- # set(GPUCA_HIP_HIPIFY_FROM_CUDA 0) # Use local HIP source files @@ -213,7 +219,7 @@ endif() # Setting target architecture and adding GPU libraries target_link_libraries(${targetName} PRIVATE hip::host hip::device hip::hipcub roc::rocthrust) -set_target_hip_arch(${targetName}) +set_target_gpu_arch("HIP" ${targetName}) target_link_libraries(${MODULE}_CXX PRIVATE TBB::tbb) diff --git a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPIncludesSystem.h b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPIncludesSystem.h index 0228f993aaee3..389c79c0e4eb9 100644 --- a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPIncludesSystem.h +++ b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPIncludesSystem.h @@ -22,11 +22,8 @@ #include #include #include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wshadow" // FIXME: Is this still needed? #include #include #include -#pragma GCC diagnostic pop #endif // O2_GPU_RECONSTRUCTIONHIPINCLUDESSYSTEM_H diff --git a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip index 30a84dfa135eb..7cb895cadd770 100644 --- a/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip +++ b/GPU/GPUTracking/Base/hip/GPUReconstructionHIPkernel.template.hip @@ -14,7 +14,7 @@ #define GPUCA_GPUCODE_COMPILEKERNELS #include "GPUReconstructionHIPIncludesSystem.h" -#define GPUCA_KRNL_REG(args) __launch_bounds__(GPUCA_M_MAX2_3(GPUCA_M_STRIP(args))) +#define GPUCA_KRNL_REG(...) GPUCA_KRNL_REG_DEFAULT(__VA_ARGS__) #define GPUCA_KRNL(...) GPUCA_KRNLGPU(__VA_ARGS__); #include "GPUReconstructionHIPDef.h" #include "GPUReconstructionKernelMacros.h" diff --git a/GPU/GPUTracking/Base/hip/test/testGPUsortHIP.hip.cxx b/GPU/GPUTracking/Base/hip/test/testGPUsortHIP.hip similarity index 100% rename from GPU/GPUTracking/Base/hip/test/testGPUsortHIP.hip.cxx rename to GPU/GPUTracking/Base/hip/test/testGPUsortHIP.hip diff --git a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.cxx b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.cxx index 271fe494860cd..6954cfb3d6211 100644 --- a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.cxx +++ b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.cxx @@ -470,27 +470,6 @@ void GPUReconstructionOCL::ReleaseEvent(deviceEvent ev) { GPUChkErr(clReleaseEve void GPUReconstructionOCL::RecordMarker(deviceEvent* ev, int32_t stream) { GPUChkErr(clEnqueueMarkerWithWaitList(mInternals->command_queue[stream], 0, nullptr, ev->getEventList())); } -int32_t GPUReconstructionOCL::DoStuckProtection(int32_t stream, deviceEvent event) -{ - if (GetProcessingSettings().stuckProtection) { - cl_int tmp = 0; - for (int32_t i = 0; i <= GetProcessingSettings().stuckProtection / 50; i++) { - usleep(50); - clGetEventInfo(event.get(), CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof(tmp), &tmp, nullptr); - if (tmp == CL_COMPLETE) { - break; - } - } - if (tmp != CL_COMPLETE) { - mGPUStuck = 1; - GPUErrorReturn("GPU Stuck, future processing in this component is disabled, skipping event (GPU Event State %d)", (int32_t)tmp); - } - } else { - clFinish(mInternals->command_queue[stream]); - } - return 0; -} - void GPUReconstructionOCL::SynchronizeGPU() { for (int32_t i = 0; i < mNStreams; i++) { diff --git a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.h b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.h index 958d5186bf41a..a52db1f2a737a 100644 --- a/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.h +++ b/GPU/GPUTracking/Base/opencl/GPUReconstructionOCL.h @@ -43,7 +43,6 @@ class GPUReconstructionOCL : public GPUReconstructionProcessing::KernelInterface virtual int32_t GPUChkErrInternal(const int64_t error, const char* file, int32_t line) const override; void SynchronizeGPU() override; - int32_t DoStuckProtection(int32_t stream, deviceEvent event) override; int32_t GPUDebug(const char* state = "UNKNOWN", int32_t stream = -1, bool force = false) override; void SynchronizeStream(int32_t stream) override; void SynchronizeEvents(deviceEvent* evList, int32_t nEvents = 1) override; 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 a7159549322a0..ca58d91212084 100644 --- a/GPU/GPUTracking/CMakeLists.txt +++ b/GPU/GPUTracking/CMakeLists.txt @@ -59,16 +59,17 @@ set(SRCS Merger/GPUTPCGMPhysicalTrackModel.cxx Merger/GPUTPCGMPolynomialFieldManager.cxx DataTypes/GPUTRDTrack.cxx + DataTypes/GPUTRDRecoParam.cxx TRDTracking/GPUTRDTracker.cxx TRDTracking/GPUTRDTrackletWord.cxx TRDTracking/GPUTRDTrackerKernels.cxx Base/GPUParam.cxx) -set(SRCS_DATATYPES DataTypes/GPUDataTypes.cxx DataTypes/GPUConfigDump.cxx DataTypes/GPUTPCGMPolynomialField.cxx) +set(SRCS_DATATYPES DataTypes/GPUDataTypesConfig.cxx DataTypes/GPUConfigDump.cxx DataTypes/GPUTPCGMPolynomialField.cxx) set(HDRS_CINT_O2 Merger/GPUTPCGMTrackParam.h Merger/GPUTPCGMMergedTrack.h Merger/GPUTPCGMSectorTrack.h Merger/GPUTPCGMBorderTrack.h TRDTracking/GPUTRDInterfaces.h) set(HDRS_CINT_DATATYPES DataTypes/GPUTPCGMMergedTrackHit.h) -set(HDRS_CINT_O2_ADDITIONAL DataTypes/GPUSettings.h Definitions/GPUSettingsList.h DataTypes/GPUDataTypes.h DataTypes/GPUTRDTrack.h DataTypes/CalibdEdxTrackTopologyPol.h DataTypes/CalibdEdxTrackTopologySpline.h) # Manual dependencies for ROOT dictionary generation +set(HDRS_CINT_O2_ADDITIONAL DataTypes/GPUSettings.h Definitions/GPUSettingsList.h DataTypes/GPUDataTypesIO.h DataTypes/GPUDataTypesConfig.h DataTypes/GPUDataTypesQA.h DataTypes/GPUTRDTrack.h DataTypes/GPUTRDRecoParam.h DataTypes/CalibdEdxTrackTopologyPol.h DataTypes/CalibdEdxTrackTopologySpline.h) # Manual dependencies for ROOT dictionary generation set(SRCS_NO_CINT DataTypes/GPUMemorySizeScalers.cxx @@ -107,6 +108,9 @@ set(SRCS_NO_H SectorTracker/GPUTPCTrackerDump.cxx Global/GPUChainTrackingIO.cxx) set(HDRS_INSTALL + ${HDRS_CINT_O2} + ${HDRS_CINT_DATATYPES} + ${HDRS_CINT_O2_ADDITIONAL} Base/GPUConstantMem.h Base/GPUParam.inc Base/GPUParamRTC.h @@ -123,28 +127,23 @@ set(HDRS_INSTALL DataTypes/GPUHostDataTypes.h DataTypes/GPUO2DataTypes.h DataTypes/GPUOutputControl.h - DataTypes/GPUSettings.h DataTypes/GPUTPCGeometry.h - DataTypes/GPUTPCGMMergedTrackHit.h DataTypes/GPUTRDDef.h DataTypes/GPUTRDInterfaceO2Track.h DataTypes/GPUTriggerOutputs.h DataTypes/GPUKernelClassesFwd.h + DataTypes/GPUO2ExternalUser.h Debug/GPUROOTDump.h Definitions/GPUDefConstantsAndSettings.h Definitions/GPUDefParametersWrapper.h Definitions/GPUDefParametersConstants.h - Definitions/GPUDefParametersDefaults.h Definitions/GPUDef.h Definitions/GPUDefMacros.h Definitions/GPULogging.h - Definitions/GPUSettingsList.h Global/GPUChainTrackingDefs.h Global/GPUChainTrackingDebug.h Global/GPUChainTrackingGetters.inc Global/GPUErrorCodes.h - Merger/GPUTPCGMBorderTrack.h - Merger/GPUTPCGMMergedTrack.h Merger/GPUTPCGMMergerTypes.h qa/GPUQAHelper.h qconfigoptions.h @@ -159,7 +158,6 @@ set(HDRS_INSTALL SectorTracker/GPUTPCTrackLinearisation.h TPCConvert/GPUTPCConvertImpl.h TRDTracking/GPUTRDGeometry.h - TRDTracking/GPUTRDInterfaces.h TRDTracking/GPUTRDSpacePoint.h TRDTracking/GPUTRDTrackData.h TRDTracking/GPUTRDTrackerDebug.h @@ -174,8 +172,6 @@ set(SRCS_NO_CINT ${SRCS_NO_CINT} display/GPUDisplayInterface.cxx) set(SRCS_NO_CINT ${SRCS_NO_CINT} Global/GPUChainITS.cxx dEdx/GPUdEdx.cxx - TPCConvert/GPUTPCConvert.cxx - TPCConvert/GPUTPCConvertKernel.cxx DataCompression/GPUTPCCompression.cxx DataCompression/GPUTPCCompressionTrackModel.cxx DataCompression/GPUTPCCompressionKernels.cxx @@ -210,6 +206,7 @@ set(SRCS_DATATYPES DataTypes/TPCPadBitMap.cxx DataTypes/TPCZSLinkMapping.cxx DataTypes/CalibdEdxContainer.cxx + DataTypes/ORTRootSerializer.cxx DataTypes/CalibdEdxTrackTopologyPol.cxx DataTypes/CalibdEdxTrackTopologySpline.cxx DataTypes/GPUTRDTrackO2.cxx) @@ -230,9 +227,11 @@ set(HDRS_INSTALL ${HDRS_INSTALL} if(ALIGPU_BUILD_TYPE STREQUAL "O2") set(SRCS_DATATYPES ${SRCS_DATATYPES} - Interface/GPUO2InterfaceConfigurableParam.cxx) + DataTypes/GPUO2ConfigurableParam.cxx) endif() +set(ON_THE_FLY_DIR ${CMAKE_CURRENT_BINARY_DIR}/include_gpu_onthefly) +file(MAKE_DIRECTORY ${ON_THE_FLY_DIR}) set(TEMPLATE_HEADER_LIST Base/GPUReconstructionKernelList.template.h Base/GPUReconstructionKernelIncludes.template.h Base/GPUReconstructionIncludesDeviceAll.template.h @@ -241,8 +240,6 @@ set(TEMPLATE_HEADER_LIST Base/GPUReconstructionKernelList.template.h Definitions/GPUDefParametersLoad.template.inc) set(GENERATED_HEADERS_LIST "") -set(ON_THE_FLY_DIR ${CMAKE_CURRENT_BINARY_DIR}/include_gpu_onthefly) -file(MAKE_DIRECTORY ${ON_THE_FLY_DIR}) foreach(TEMPLATE_FILE ${TEMPLATE_HEADER_LIST}) get_filename_component(OUTPUT_FILE_NAME ${TEMPLATE_FILE} NAME) string(REPLACE ".template" "" OUTPUT_FILE_NAME ${OUTPUT_FILE_NAME}) @@ -265,6 +262,48 @@ add_custom_command( ) list(APPEND GENERATED_HEADERS_LIST ${ON_THE_FLY_DIR}/GPUDefParametersLoadPrepare.h) +if(GPUCA_OVERRIDE_PARAMETER_FILE) + set(GPU_PARAM_JSON ${GPUCA_OVERRIDE_PARAMETER_FILE}) +else() + set(GPU_PARAM_JSON ${CMAKE_CURRENT_SOURCE_DIR}/Definitions/Parameters/GPUParameters.csv) +endif() +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${GPU_PARAM_JSON}") + +set(GPU_PARAM_JSON_FILES) +set(GPU_PARAM_JSON_N_FILES 0) +foreach(GPU_PARAM_JSON_FILE IN LISTS GPU_PARAM_JSON) + if(NOT EXISTS "${GPU_PARAM_JSON_FILE}") + message(FATAL_ERROR "Parameter file ${GPU_PARAM_JSON_FILE} does not exist") + endif() + get_filename_component(GPU_PARAM_JSON_EXT ${GPU_PARAM_JSON_FILE} EXT) + string(TOLOWER "${GPU_PARAM_JSON_EXT}" GPU_PARAM_JSON_EXT) + if(GPU_PARAM_JSON_EXT STREQUAL .csv) + get_filename_component(GPU_PARAM_JSON_NAME ${GPU_PARAM_JSON_FILE} NAME_WE) + set(CONVOUTFILE "GPUParameters_${GPU_PARAM_JSON_NAME}_${GPU_PARAM_JSON_N_FILES}.json") + execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/Definitions/Parameters/csv_to_json.sh "${GPU_PARAM_JSON_FILE}" + OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${CONVOUTFILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND_ERROR_IS_FATAL ANY + ) + message(STATUS "Converted ${GPU_PARAM_JSON_FILE} to ${CONVOUTFILE}") + list(APPEND GPU_PARAM_JSON_FILES ${CMAKE_CURRENT_BINARY_DIR}/${CONVOUTFILE}) + else() + list(APPEND GPU_PARAM_JSON_FILES ${GPU_PARAM_JSON_FILE}) + endif() + math(EXPR GPU_PARAM_JSON_N_FILES "${GPU_PARAM_JSON_N_FILES} + 1") +endforeach() + +include(Definitions/Parameters/gpu_param_header_generator.cmake) +set(GPU_DEFAULT_PARAMS_HEADER ${ON_THE_FLY_DIR}/GPUDefParametersDefaults.h) +set(GPU_DEFAULT_PARAMS_HEADER_DEVICE ${ON_THE_FLY_DIR}/GPUDefParametersDefaultsDevice.h) +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) @@ -288,6 +327,7 @@ set(HDRS_CINT_DATATYPES ${HDRS_CINT_DATATYPES} ${HDRS_TMP}) unset(HDRS_TMP) set(INCDIRS + ${ON_THE_FLY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/Definitions ${CMAKE_CURRENT_SOURCE_DIR}/DataTypes @@ -304,23 +344,24 @@ set(INCDIRS ${CMAKE_CURRENT_SOURCE_DIR}/Refit ${CMAKE_CURRENT_SOURCE_DIR}/Debug ${CMAKE_CURRENT_SOURCE_DIR}/DataCompression - ${CMAKE_CURRENT_SOURCE_DIR}/TPCClusterFinder - ${ON_THE_FLY_DIR}) + ${CMAKE_CURRENT_SOURCE_DIR}/TPCClusterFinder) # Main CMake part for O2 if(ALIGPU_BUILD_TYPE STREQUAL "O2") o2_add_library(GPUDataTypes TARGETVARNAME targetName PUBLIC_INCLUDE_DIRECTORIES . + ${ON_THE_FLY_DIR} Definitions DataTypes 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} @@ -349,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} @@ -406,30 +447,6 @@ target_sources(${targetName} FILES ${GENERATED_HEADERS_LIST} BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) -make_directory(${CMAKE_CURRENT_BINARY_DIR}/genGPUArch) -set(GPU_CONST_PARAM_ARCHITECTUES AMPERE TURING VEGA MI2xx) -set(GPU_CONST_PARAM_FILES "") -foreach(GPU_ARCH ${GPU_CONST_PARAM_ARCHITECTUES}) - set(PARAMFILE ${CMAKE_CURRENT_BINARY_DIR}/genGPUArch/gpu_const_param_${GPU_ARCH}.par) - add_custom_command( - OUTPUT ${PARAMFILE} - COMMAND bash -c - "echo -e '#define GPUCA_GPUTYPE_${GPU_ARCH}\\n#define PARAMETER_FILE \"GPUDefParametersDefaults.h\"\\ngInterpreter->AddIncludePath(\"${CMAKE_CURRENT_SOURCE_DIR}/Definitions\");\\ngInterpreter->AddIncludePath(\"${ON_THE_FLY_DIR}\");\\n.x ${CMAKE_CURRENT_SOURCE_DIR}/Standalone/tools/dumpGPUDefParam.C(\"${PARAMFILE}\")\\n.q\\n'" - | root -l -b > /dev/null - VERBATIM - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/genGPUArch - MAIN_DEPENDENCY Standalone/tools/dumpGPUDefParam.C - DEPENDS Definitions/GPUDefParametersDefaults.h - ${ON_THE_FLY_DIR}/GPUDefParametersLoadPrepare.h - ${ON_THE_FLY_DIR}/GPUDefParametersLoad.inc - COMMENT "Generating GPU parameter set for architecture ${GPU_ARCH}") - LIST(APPEND GPU_CONST_PARAM_FILES ${PARAMFILE}) -endforeach() - -add_custom_target(${MODULE}_GPU_CONST_PARAM_ARCHS ALL DEPENDS ${GPU_CONST_PARAM_FILES}) -install(FILES ${GPU_CONST_PARAM_FILES} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/GPU/arch_param) - - # Add compile definitions and libraries depending on available optional dependencies if(GPUCA_QA) message(STATUS "Building GPU QA") @@ -450,6 +467,28 @@ if(CUDA_ENABLED OR OPENCL_ENABLED OR HIP_ENABLED) if(CMAKE_SYSTEM_NAME MATCHES Darwin) message(WARNING "GPU Tracking disabled on MacOS") else() + make_directory(${CMAKE_CURRENT_BINARY_DIR}/genGPUArch) + set(GPU_CONST_PARAM_FILES) + foreach(GPU_ARCH ${GPU_CONST_PARAM_ARCHITECTUES}) + set(PARAMFILE ${CMAKE_CURRENT_BINARY_DIR}/genGPUArch/gpu_const_param_${GPU_ARCH}.par) + add_custom_command( + OUTPUT ${PARAMFILE} + COMMAND bash -c + "echo -e '#define GPUCA_GPUTYPE_${GPU_ARCH}\\n#define PARAMETER_FILE \"GPUDefParametersDefaults.h\"\\ngInterpreter->AddIncludePath(\"${CMAKE_CURRENT_SOURCE_DIR}/Definitions\");\\ngInterpreter->AddIncludePath(\"${ON_THE_FLY_DIR}\");\\n.x ${CMAKE_CURRENT_SOURCE_DIR}/Standalone/tools/dumpGPUDefParam.C(\"${PARAMFILE}\")\\n.q\\n'" + | root -l -b > /dev/null + VERBATIM + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/genGPUArch + MAIN_DEPENDENCY Standalone/tools/dumpGPUDefParam.C + DEPENDS ${GPU_DEFAULT_PARAMS_HEADER} + ${GPU_DEFAULT_PARAMS_HEADER_DEVICE} + ${ON_THE_FLY_DIR}/GPUDefParametersLoadPrepare.h + ${ON_THE_FLY_DIR}/GPUDefParametersLoad.inc + COMMENT "Generating GPU parameter set for architecture ${GPU_ARCH}") + LIST(APPEND GPU_CONST_PARAM_FILES ${PARAMFILE}) + endforeach() + add_custom_target(${MODULE}_GPU_CONST_PARAM_ARCHS ALL DEPENDS ${GPU_CONST_PARAM_FILES}) + install(FILES ${GPU_CONST_PARAM_FILES} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/GPU/arch_param) + if(CUDA_ENABLED) add_subdirectory(Base/cuda) endif() diff --git a/GPU/GPUTracking/DataCompression/GPUTPCClusterRejection.h b/GPU/GPUTracking/DataCompression/GPUTPCClusterRejection.h index 5c25813e75d29..8ec8c85c4c73f 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCClusterRejection.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCClusterRejection.h @@ -16,29 +16,36 @@ #define GPUTPCCLUSTERREJECTION_H #include "GPUTPCGMMergerTypes.h" +#include "GPUCommonMath.h" namespace o2::gpu { struct GPUTPCClusterRejection { + template + GPUdi() static bool IsTrackRejected(const T& trk, const S& param) + { + return CAMath::Abs(trk.GetParam().GetQPt() * param.qptB5Scaler) > param.rec.tpc.rejectQPtB5 || trk.MergedLooper(); + } + template - static constexpr inline bool GetProtectionStatus(int32_t attach, bool& physics, bool& protect, T* counts = nullptr, S* mev200 = nullptr) + GPUdi() static constexpr bool GetRejectionStatus(int32_t attach, bool& physics, T* counts = nullptr, S* mev200 = nullptr) { - (void)counts; // Avoid incorrect -Wunused-but-set-parameter warning + (void)counts; // FIXME: Avoid incorrect -Wunused-but-set-parameter warning (void)mev200; + bool retVal = false; if (attach == 0) { - return false; + retVal = false; } else if ((attach & gputpcgmmergertypes::attachGoodLeg) == 0) { if constexpr (C) { counts->nLoopers++; } - return true; + retVal = true; } else if (attach & gputpcgmmergertypes::attachHighIncl) { if constexpr (C) { counts->nHighIncl++; } - return true; + retVal = true; } else if (attach & gputpcgmmergertypes::attachTube) { - protect = true; if constexpr (C) { if (*mev200) { counts->nTube200++; @@ -46,23 +53,27 @@ struct GPUTPCClusterRejection { counts->nTube++; } } - return false; + retVal = false; } else if ((attach & gputpcgmmergertypes::attachGood) == 0) { - protect = true; if constexpr (C) { counts->nRejected++; } - return false; + retVal = false; } else { physics = true; - return false; + retVal = false; + } + + if (attach & gputpcgmmergertypes::attachProtect) { + retVal = false; } + return retVal; } - static constexpr inline bool GetIsRejected(int32_t attach) + GPUdi() static constexpr bool GetIsRejected(int32_t attach) { - bool physics = false, protect = false; - return GetProtectionStatus(attach, physics, protect); + bool physics = false; + return GetRejectionStatus(attach, physics); } }; } // namespace o2::gpu 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 61f8a614fbe6f..00f1f6500e9f0 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompression.cxx @@ -38,7 +38,7 @@ void* GPUTPCCompression::SetPointersOutputHost(void* mem) void* GPUTPCCompression::SetPointersScratch(void* mem) { - int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & GPUDataTypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; + int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & gpudatatypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; computePointerWithAlignment(mem, mClusterStatus, mMaxClusters); if (gatherMode >= 2) { computePointerWithAlignment(mem, mAttachedClusterFirstIndex, mMaxTracks); @@ -51,7 +51,7 @@ void* GPUTPCCompression::SetPointersScratch(void* mem) void* GPUTPCCompression::SetPointersOutput(void* mem) { - int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & GPUDataTypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; + int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & gpudatatypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; computePointerWithAlignment(mem, mAttachedClusterFirstIndex, mMaxTrackClusters); if (gatherMode == 1) { SetPointersCompressedClusters(mem, mPtrs, mMaxTrackClusters, mMaxTracks, mMaxClustersInCache, false); @@ -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; @@ -106,12 +106,12 @@ void* GPUTPCCompression::SetPointersMemory(void* mem) void GPUTPCCompression::RegisterMemoryAllocation() { AllocateAndInitializeLate(); - int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & GPUDataTypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; + int32_t gatherMode = mRec->GetProcessingSettings().tpcCompressionGatherMode == -1 ? mRec->getGPUParameters(mRec->GetRecoStepsGPU() & gpudatatypes::RecoStep::TPCCompression).par_COMP_GATHER_MODE : mRec->GetProcessingSettings().tpcCompressionGatherMode; mMemoryResOutputHost = mRec->RegisterMemoryAllocation(this, &GPUTPCCompression::SetPointersOutputHost, GPUMemoryResource::MEMORY_OUTPUT_FLAG | GPUMemoryResource::MEMORY_HOST | GPUMemoryResource::MEMORY_CUSTOM, "TPCCompressionOutputHost"); if (gatherMode == 3) { mMemoryResOutputGPU = mRec->RegisterMemoryAllocation(this, &GPUTPCCompression::SetPointersOutputGPU, GPUMemoryResource::MEMORY_SCRATCH | GPUMemoryResource::MEMORY_GPU | GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_STACK, "TPCCompressionOutputGPU"); } - uint32_t stackScratch = (gatherMode != 3) ? GPUMemoryResource::MEMORY_STACK : 0; + uint32_t stackScratch = (gatherMode != 3) ? GPUMemoryResource::MEMORY_STACK : 0; // TODO: Can we use stacked memory also with gather mode 3? if (gatherMode < 2) { mRec->RegisterMemoryAllocation(this, &GPUTPCCompression::SetPointersOutput, GPUMemoryResource::MEMORY_OUTPUT | stackScratch, "TPCCompressionOutput"); } @@ -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 52585b4c08b24..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 { @@ -60,6 +61,7 @@ class GPUTPCCompression : public GPUProcessor #ifndef GPUCA_GPUCODE void DumpCompressedClusters(std::ostream& out); #endif + GPUd() bool rejectCluster(int32_t idx, const GPUParam& param, const GPUTrackingInOutPointers& ioPtrs) const; protected: struct memory { @@ -68,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 73b195e8f4fe4..b98f5c28f57b0 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.cxx @@ -32,7 +32,6 @@ GPUdii() void GPUTPCCompressionKernels::Thread processors.param.rec.tpc.rejectQPtB5 || trk.MergedLooper(); + bool rejectTrk = GPUTPCClusterRejection::IsTrackRejected(trk, param); uint32_t nClustersStored = 0; CompressedClustersPtrs& GPUrestrict() c = compressor.mPtrs; uint8_t lastRow = 0, lastSector = 0; @@ -57,10 +56,12 @@ GPUdii() void GPUTPCCompressionKernels::Thread= GPUSettings::RejectionStrategyA && (rejectTrk || GPUTPCClusterRejection::GetIsRejected(attach)); + bool rejectCluster = processors.param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyA && !(attach & gputpcgmmergertypes::attachProtect) && (rejectTrk || GPUTPCClusterRejection::GetIsRejected(attach)); if (rejectCluster) { compressor.mClusterStatus[hitId] = 1; // Cluster rejected, do not store continue; + } else if (processors.param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyA && rejectTrk) { + continue; } if (!(param.rec.tpc.compressionTypeMask & GPUSettings::CompressionTrackModel)) { @@ -72,13 +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(); - lastLeg = hit.leg; c.qPtA[myTrack] = qpt; c.rowA[myTrack] = hit.row; c.sliceA[myTrack] = hit.sector; @@ -105,7 +102,7 @@ GPUdii() void GPUTPCCompressionKernels::Thread row) { - row += GPUCA_ROW_COUNT; + row += GPUTPCGeometry::NROWS; } row -= lastRow; if (lastSector > sector) { @@ -114,12 +111,11 @@ GPUdii() void GPUTPCCompressionKernels::Thread::opera return mClsPtr[a].qTot < mClsPtr[b].qTot; } +GPUd() bool GPUTPCCompression::rejectCluster(int32_t idx, const GPUParam& GPUrestrict() param, const GPUTrackingInOutPointers& GPUrestrict() ioPtrs) const +{ + if (mClusterStatus[idx]) { + return true; + } + int32_t attach = ioPtrs.mergedTrackHitAttachment[idx]; + bool unattached = attach == 0; + + if (unattached) { + if (param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyB) { + return true; + } + } else if (param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyA) { + if (GPUTPCClusterRejection::GetIsRejected(attach)) { + return true; + } + if (attach & gputpcgmmergertypes::attachProtect) { + return false; + } + int32_t id = attach & gputpcgmmergertypes::attachTrackMask; + auto& trk = ioPtrs.mergedTracks[id]; + if (GPUTPCClusterRejection::IsTrackRejected(trk, param)) { + return true; + } + } + return false; +} + template <> GPUdii() void GPUTPCCompressionKernels::Thread(int32_t nBlocks, int32_t nThreads, int32_t iBlock, int32_t iThread, GPUsharedref() GPUSharedMemory& smem, processorType& GPUrestrict() processors) { @@ -197,9 +221,9 @@ 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 @@ -214,42 +238,16 @@ GPUdii() void GPUTPCCompressionKernels::Thread(clusters->nClusters[iSector][iRow]); for (uint32_t i = iThread; i < nn + nThreads; i += nThreads) { const int32_t idx = idOffset + i; - int32_t storeCluster = 0; - do { - if (i >= clusters->nClusters[iSector][iRow]) { - break; - } - if (compressor.mClusterStatus[idx]) { - break; - } - int32_t attach = ioPtrs.mergedTrackHitAttachment[idx]; - bool unattached = attach == 0; - - if (unattached) { - if (processors.param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyB) { - break; - } - } else if (processors.param.rec.tpc.rejectionStrategy >= GPUSettings::RejectionStrategyA) { - if (GPUTPCClusterRejection::GetIsRejected(attach)) { - break; - } - int32_t id = attach & gputpcgmmergertypes::attachTrackMask; - auto& trk = ioPtrs.mergedTracks[id]; - if (CAMath::Abs(trk.GetParam().GetQPt() * processors.param.qptB5Scaler) > processors.param.rec.tpc.rejectQPtB5 || trk.MergedLooper()) { - break; - } - } - storeCluster = 1; - } while (false); + int32_t storeCluster = i < clusters->nClusters[iSector][iRow] && !compressor.rejectCluster(idx, param, ioPtrs); GPUbarrier(); int32_t myIndex = work_group_scan_inclusive_add(storeCluster); int32_t storeLater = -1; if (storeCluster) { - if (smem.nCount + myIndex <= GPUCA_TPC_COMP_CHUNK_SIZE) { + if (smem.nCount + myIndex <= constants::TPC_COMP_CHUNK_SIZE) { sortBuffer[smem.nCount + myIndex - 1] = i; } else { - storeLater = smem.nCount + myIndex - 1 - GPUCA_TPC_COMP_CHUNK_SIZE; + storeLater = smem.nCount + myIndex - 1 - constants::TPC_COMP_CHUNK_SIZE; } } GPUbarrier(); @@ -258,11 +256,11 @@ GPUdii() void GPUTPCCompressionKernels::Thread idOffsetOutMax) { if (iThread == nThreads - 1) { compressor.raiseError(GPUErrors::ERROR_COMPRESSION_ROW_HIT_OVERFLOW, iSector * 1000 + iRow, idOffsetOut + totalCount + count, idOffsetOutMax); @@ -271,9 +269,9 @@ GPUdii() void GPUTPCCompressionKernels::Thread(clusters->clusters[iSector][iRow])); #else // GPUCA_DETERMINISTIC_MODE if (param.rec.tpc.compressionSortOrder == GPUSettings::SortZPadTime) { @@ -323,7 +321,7 @@ GPUdii() void GPUTPCCompressionKernels::ThreadnStoredUnattachedClusters, totalCount); } GPUbarrier(); @@ -549,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); @@ -568,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); @@ -655,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); @@ -680,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); @@ -733,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); @@ -744,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 81817abf1e6d6..1edf718de8128 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCCompressionKernels.h @@ -27,7 +27,7 @@ namespace o2::gpu class GPUTPCCompressionKernels : public GPUKernelTemplate { public: - GPUhdi() constexpr static GPUDataTypes::RecoStep GetRecoStep() { return GPUDataTypes::RecoStep::TPCCompression; } + GPUhdi() constexpr static gpudatatypes::RecoStep GetRecoStep() { return gpudatatypes::RecoStep::TPCCompression; } enum K : int32_t { step0attached = 0, @@ -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 fd0c929dd2ba7..b44cdb420d74b 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompression.cxx @@ -18,6 +18,7 @@ #include "GPUO2DataTypes.h" #include "GPUMemorySizeScalers.h" #include "GPULogging.h" +#include using namespace o2::gpu; @@ -39,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; @@ -67,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; } @@ -98,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; } @@ -106,15 +107,21 @@ void GPUTPCDecompression::RegisterMemoryAllocation() { AllocateAndInitializeLate(); mMemoryResInputGPU = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersInputGPU, GPUMemoryResource::MEMORY_INPUT_FLAG | GPUMemoryResource::MEMORY_GPU | GPUMemoryResource::MEMORY_EXTERNAL | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionInput"); - mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersGPU, GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpBuffersGPU"); - mResourceTmpIndexes = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersOutput, GPUMemoryResource::MEMORY_OUTPUT | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpBuffersOutput"); - mResourceTmpClustersOffsets = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersInput, GPUMemoryResource::MEMORY_INPUT | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpBuffersInput"); - mResourceTmpBufferBeforeFiltering = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpClusterNativeAccessForFiltering, GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpBufferForFiltering"); - mResourceClusterNativeAccess = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersInputClusterNativeAccess, GPUMemoryResource::MEMORY_INPUT | GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpClusterAccessForFiltering"); - mResourceNClusterPerSectorRow = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersNClusterPerSectorRow, GPUMemoryResource::MEMORY_OUTPUT | GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_SCRATCH, "TPCDecompressionTmpClusterCountForFiltering"); + mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersGPU, GPUMemoryResource::MEMORY_SCRATCH | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpBuffersGPU"); + mResourceTmpIndexes = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersOutput, GPUMemoryResource::MEMORY_OUTPUT | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpBuffersOutput"); + mResourceTmpClustersOffsets = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpNativeBuffersInput, GPUMemoryResource::MEMORY_INPUT | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpBuffersInput"); + mResourceTmpBufferBeforeFiltering = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersTmpClusterNativeAccessForFiltering, GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_SCRATCH | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpBufferForFiltering"); + mResourceClusterNativeAccess = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersInputClusterNativeAccess, GPUMemoryResource::MEMORY_INPUT | GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpClusterAccessForFiltering"); + mResourceNClusterPerSectorRow = mRec->RegisterMemoryAllocation(this, &GPUTPCDecompression::SetPointersNClusterPerSectorRow, GPUMemoryResource::MEMORY_OUTPUT | GPUMemoryResource::MEMORY_CUSTOM | GPUMemoryResource::MEMORY_STACK, "TPCDecompressionTmpClusterCountForFiltering"); } void GPUTPCDecompression::SetMaxData(const GPUTrackingInOutPointers& io) { - mMaxNativeClustersPerBuffer = mRec->GetProcessingSettings().tpcMaxAttachedClustersPerSectorRow; + 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) * mRec->MemoryScalers()->tpcDecodingClusterRatioFactor1; + maxAttachedClsMargin1 *= clsRatio1; + 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 1ea93e4acb9d0..03fcfebacdaa9 100644 --- a/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h +++ b/GPU/GPUTracking/DataCompression/GPUTPCDecompressionKernels.h @@ -27,7 +27,7 @@ namespace o2::gpu class GPUTPCDecompressionKernels : public GPUKernelTemplate { public: - GPUhdi() constexpr static GPUDataTypes::RecoStep GetRecoStep() { return GPUDataTypes::RecoStep::TPCDecompression; } + GPUhdi() constexpr static gpudatatypes::RecoStep GetRecoStep() { return gpudatatypes::RecoStep::TPCDecompression; } enum K : int32_t { step0attached = 0, @@ -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 cd1717faf178d..e28b4c476f815 100644 --- a/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx +++ b/GPU/GPUTracking/DataCompression/TPCClusterDecompressor.cxx @@ -43,17 +43,17 @@ int32_t TPCClusterDecompressor::decompress(const CompressedClustersFlat* cluster int32_t TPCClusterDecompressor::decompress(const CompressedClusters* clustersCompressed, o2::tpc::ClusterNativeAccess& clustersNative, std::function allocator, const GPUParam& param, bool deterministicRec) { if (clustersCompressed->nTracks && clustersCompressed->solenoidBz != -1e6f && clustersCompressed->solenoidBz != param.bzkG) { - throw std::runtime_error("Configured solenoid Bz does not match value used for track model encoding"); + throw std::runtime_error("Configured solenoid Bz " + std::to_string(param.bzkG) + " does not match value used for track model encoding " + std::to_string(clustersCompressed->solenoidBz)); } if (clustersCompressed->nTracks && clustersCompressed->maxTimeBin != -1e6 && clustersCompressed->maxTimeBin != param.continuousMaxTimeBin) { - throw std::runtime_error("Configured max time bin does not match value used for track model encoding"); + 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/GPUConfigDump.cxx b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx index 7ec2df3a2f186..56543d5f2e43d 100644 --- a/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx +++ b/GPU/GPUTracking/DataTypes/GPUConfigDump.cxx @@ -13,7 +13,7 @@ /// \author David Rohr #include "GPUConfigDump.h" -#include "GPUDataTypes.h" +#include "GPUDataTypesIO.h" #include "GPUSettings.h" #include diff --git a/GPU/GPUTracking/DataTypes/GPUDataTypes.cxx b/GPU/GPUTracking/DataTypes/GPUDataTypesConfig.cxx similarity index 73% rename from GPU/GPUTracking/DataTypes/GPUDataTypes.cxx rename to GPU/GPUTracking/DataTypes/GPUDataTypesConfig.cxx index c544ac610cdfa..80ca919dd29e1 100644 --- a/GPU/GPUTracking/DataTypes/GPUDataTypes.cxx +++ b/GPU/GPUTracking/DataTypes/GPUDataTypesConfig.cxx @@ -9,19 +9,15 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file GPUDataTypes.cxx +/// \file GPUDataTypesConfig.cxx /// \author David Rohr -#include "GPUDataTypes.h" +#include "GPUDataTypesConfig.h" #include using namespace o2::gpu; -constexpr const char* const GPUDataTypes::DEVICE_TYPE_NAMES[]; -constexpr const char* const GPUDataTypes::RECO_STEP_NAMES[]; -constexpr const char* const GPUDataTypes::GENERAL_STEP_NAMES[]; - -GPUDataTypes::DeviceType GPUDataTypes::GetDeviceType(const char* type) +gpudatatypes::DeviceType gpudatatypes::GetDeviceType(const char* type) { for (uint32_t i = 1; i < sizeof(DEVICE_TYPE_NAMES) / sizeof(DEVICE_TYPE_NAMES[0]); i++) { if (strcmp(DEVICE_TYPE_NAMES[i], type) == 0) { diff --git a/GPU/GPUTracking/DataTypes/GPUDataTypesConfig.h b/GPU/GPUTracking/DataTypes/GPUDataTypesConfig.h new file mode 100644 index 0000000000000..6535bb93770c4 --- /dev/null +++ b/GPU/GPUTracking/DataTypes/GPUDataTypesConfig.h @@ -0,0 +1,80 @@ +// 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 GPUDataTypesConfig.h +/// \author David Rohr + +#ifndef GPUDATATYPESCONFIG_H +#define GPUDATATYPESCONFIG_H + +#include "GPUCommonDef.h" + +// These are basic and non-complex data types, which will also be visible on the GPU. +// Please add complex data types required on the host but not GPU to GPUHostDataTypes.h and forward-declare! +#ifndef GPUCA_GPUCODE_DEVICE +#include // for bitfield below +#include +#endif + +namespace o2::gpu +{ +#include "utils/bitfield.h" + +namespace gpudatatypes +{ +// clang-format off +enum class GeometryType : uint32_t { RESERVED_GEOMETRY = 0, ALIROOT = 1, O2 = 2 }; +enum DeviceType : uint32_t { INVALID_DEVICE = 0, CPU = 1, CUDA = 2, HIP = 3, OCL = 4 }; +enum class GeneralStep : uint32_t { Prepare = 1, QA = 2 }; +// clang-format on + +enum class RecoStep : uint32_t { TPCConversion = 1, + TPCSectorTracking = 2, + TPCMerging = 4, + TPCCompression = 8, + TRDTracking = 16, + ITSTracking = 32, + TPCdEdx = 64, + TPCClusterFinding = 128, + TPCDecompression = 256, + Refit = 512, + AllRecoSteps = 0x7FFFFFFF, + NoRecoStep = 0 }; +enum class InOutType : uint32_t { TPCClusters = 1, + OBSOLETE = 2, + TPCMergedTracks = 4, + TPCCompressedClusters = 8, + TRDTracklets = 16, + TRDTracks = 32, + TPCRaw = 64, + ITSClusters = 128, + ITSTracks = 256 }; +#ifndef __OPENCL__ +static constexpr const char* const DEVICE_TYPE_NAMES[] = {"INVALID", "CPU", "CUDA", "HIP", "OCL"}; +static constexpr const char* const RECO_STEP_NAMES[] = {"TPC Transformation", "TPC Sector Tracking", "TPC Track Merging and Fit", "TPC Compression", "TRD Tracking", "ITS Tracking", "TPC dEdx Computation", "TPC Cluster Finding", "TPC Decompression", "Global Refit"}; +static constexpr const char* const GENERAL_STEP_NAMES[] = {"Prepare", "QA"}; +constexpr static int32_t N_RECO_STEPS = sizeof(gpudatatypes::RECO_STEP_NAMES) / sizeof(gpudatatypes::RECO_STEP_NAMES[0]); +constexpr static int32_t N_GENERAL_STEPS = sizeof(gpudatatypes::GENERAL_STEP_NAMES) / sizeof(gpudatatypes::GENERAL_STEP_NAMES[0]); +#endif +typedef bitfield RecoStepField; +typedef bitfield InOutTypeField; +DeviceType GetDeviceType(const char* type); +} // namespace gpudatatypes + +struct GPURecoStepConfiguration { + gpudatatypes::RecoStepField steps = 0; + gpudatatypes::RecoStepField stepsGPUMask = gpudatatypes::RecoStep::AllRecoSteps; + gpudatatypes::InOutTypeField inputs = 0; + gpudatatypes::InOutTypeField outputs = 0; +}; +} // namespace o2::gpu + +#endif diff --git a/GPU/GPUTracking/DataTypes/GPUDataTypes.h b/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h similarity index 70% rename from GPU/GPUTracking/DataTypes/GPUDataTypes.h rename to GPU/GPUTracking/DataTypes/GPUDataTypesIO.h index 3e9623e23559b..f3172aa18d387 100644 --- a/GPU/GPUTracking/DataTypes/GPUDataTypes.h +++ b/GPU/GPUTracking/DataTypes/GPUDataTypesIO.h @@ -9,21 +9,21 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -/// \file GPUDataTypes.h +/// \file GPUDataTypesIO.h /// \author David Rohr -#ifndef GPUDATATYPES_H -#define GPUDATATYPES_H +#ifndef GPUDATATYPESIO_H +#define GPUDATATYPESIO_H #include "GPUCommonDef.h" // These are basic and non-complex data types, which will also be visible on the GPU. // Please add complex data types required on the host but not GPU to GPUHostDataTypes.h and forward-declare! #ifndef GPUCA_GPUCODE_DEVICE -#include // for bitfield below #include #endif #include "GPUTRDDef.h" +#include "DataFormatsTPC/Constants.h" struct AliHLTTPCClusterMCLabel; struct AliHLTTPCRawCluster; @@ -85,23 +85,22 @@ class Cluster; namespace tpc { class CalibdEdxContainer; +class ORTRootSerializer; } // namespace tpc } // namespace o2 namespace o2::gpu { class CorrectionMapsHelper; -class TPCFastTransform; +class TPCFastTransformPOD; struct TPCPadGainCalib; struct TPCZSLinkMapping; -#include "utils/bitfield.h" - +class GPUTRDRecoParam; class GPUTPCTrack; class GPUTPCHitId; class GPUTPCGMMergedTrack; struct GPUTPCGMMergedTrackHit; -struct GPUTPCGMMergedTrackHitXYZ; class GPUTRDTrackletWord; class GPUTRDSpacePoint; struct GPUTPCMCInfo; @@ -111,56 +110,6 @@ struct GPUTRDTrackletLabels; struct GPUTPCDigitsMCInput; struct GPUSettingsTF; -class GPUDataTypes -{ - public: - // clang-format off - enum class GeometryType : uint32_t { RESERVED_GEOMETRY = 0, ALIROOT = 1, O2 = 2 }; - enum DeviceType : uint32_t { INVALID_DEVICE = 0, CPU = 1, CUDA = 2, HIP = 3, OCL = 4 }; - enum class GeneralStep { Prepare = 1, QA = 2 }; - // clang-format on - - enum class RecoStep { TPCConversion = 1, - TPCSectorTracking = 2, - TPCMerging = 4, - TPCCompression = 8, - TRDTracking = 16, - ITSTracking = 32, - TPCdEdx = 64, - TPCClusterFinding = 128, - TPCDecompression = 256, - Refit = 512, - AllRecoSteps = 0x7FFFFFFF, - NoRecoStep = 0 }; - enum class InOutType { TPCClusters = 1, - OBSOLETE = 2, - TPCMergedTracks = 4, - TPCCompressedClusters = 8, - TRDTracklets = 16, - TRDTracks = 32, - TPCRaw = 64, - ITSClusters = 128, - ITSTracks = 256 }; -#ifndef __OPENCL__ - static constexpr const char* const DEVICE_TYPE_NAMES[] = {"INVALID", "CPU", "CUDA", "HIP", "OCL"}; - static constexpr const char* const RECO_STEP_NAMES[] = {"TPC Transformation", "TPC Sector Tracking", "TPC Track Merging and Fit", "TPC Compression", "TRD Tracking", "ITS Tracking", "TPC dEdx Computation", "TPC Cluster Finding", "TPC Decompression", "Global Refit"}; - static constexpr const char* const GENERAL_STEP_NAMES[] = {"Prepare", "QA"}; - constexpr static int32_t N_RECO_STEPS = sizeof(GPUDataTypes::RECO_STEP_NAMES) / sizeof(GPUDataTypes::RECO_STEP_NAMES[0]); - constexpr static int32_t N_GENERAL_STEPS = sizeof(GPUDataTypes::GENERAL_STEP_NAMES) / sizeof(GPUDataTypes::GENERAL_STEP_NAMES[0]); -#endif - typedef bitfield RecoStepField; - typedef bitfield InOutTypeField; - static constexpr uint32_t NSECTORS = 36; - static DeviceType GetDeviceType(const char* type); -}; - -struct GPURecoStepConfiguration { - GPUDataTypes::RecoStepField steps = 0; - GPUDataTypes::RecoStepField stepsGPUMask = GPUDataTypes::RecoStep::AllRecoSteps; - GPUDataTypes::InOutTypeField inputs = 0; - GPUDataTypes::InOutTypeField outputs = 0; -}; - template struct DefaultPtr { typedef T type; @@ -172,10 +121,7 @@ struct ConstPtr { template