From becce3a00a3cd6d34ada60799c0fa802c9f1e42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Thu, 31 Jul 2025 15:14:48 +0200 Subject: [PATCH 01/12] [QC-1298] first version of Data --- Framework/CMakeLists.txt | 5 +- .../include/QualityControl/CheckInterface.h | 12 ++- Framework/include/QualityControl/Data.h | 86 +++++++++++++++++++ Framework/src/CheckInterface.cxx | 26 ++++++ Framework/src/Data.cxx | 27 ++++++ Framework/test/testData.cxx | 79 +++++++++++++++++ .../Skeleton/include/Skeleton/SkeletonCheck.h | 1 + Modules/Skeleton/src/SkeletonCheck.cxx | 61 +++++++------ 8 files changed, 268 insertions(+), 29 deletions(-) create mode 100644 Framework/include/QualityControl/Data.h create mode 100644 Framework/src/Data.cxx create mode 100644 Framework/test/testData.cxx diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index 8150becbff..c7893e4b5a 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -136,7 +136,9 @@ add_library(O2QualityControl src/ReductorHelpers.cxx src/KafkaPoller.cxx src/FlagHelpers.cxx - src/ObjectMetadataHelpers.cxx) + src/ObjectMetadataHelpers.cxx + src/Data.cxx +) target_include_directories( O2QualityControl @@ -289,6 +291,7 @@ add_executable(o2-qc-test-core test/testKafkaTests.cxx test/testFlagHelpers.cxx test/testQualitiesToFlagCollectionConverter.cxx + test/testData.cxx ) set_property(TARGET o2-qc-test-core PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index 7d78fd4bc3..3485fb4be2 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -26,7 +26,9 @@ namespace o2::quality_control::core { class Activity; class MonitorObject; -} +class Data; + +} // namespace o2::quality_control::core using namespace o2::quality_control::core; @@ -48,7 +50,13 @@ class CheckInterface : public core::UserCodeInterface /// /// @param moMap A map of the the MonitorObjects to check and their full names (i.e. /) as keys. /// @return The quality associated with these objects. - virtual core::Quality check(std::map>* moMap) = 0; + virtual core::Quality check(std::map>* moMap); + + /// \brief Returns the quality associated with these objects. + /// + /// @param data An object with any type of data possible accesible via full names (i.e. / in case of MOs) as keys. + /// @return The quality associated with these objects. + virtual core::Quality check(const core::Data& data); /// \brief Modify the aspect of the plot. /// diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h new file mode 100644 index 0000000000..da9ddb8c38 --- /dev/null +++ b/Framework/include/QualityControl/Data.h @@ -0,0 +1,86 @@ +// Copyright 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 Data.h +/// \author Michal Tichak +/// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +class MonitorObject; + +class Data +{ + public: + Data() = default; + Data(const std::map>& moMap); + + template + std::optional get(std::string_view key) + { + if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { + if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { + return { *casted }; + } + } + return std::nullopt; + } + + template + void insert(std::string_view key, const StoreType& value) + { + // std::cout << "inserting value: " << value << ", of type: " << typeid(StoreType).name() << "\n"; + mObjects.insert({ std::string{ key }, value }); + } + + template + std::vector getAllOfType() const + { + std::vector result; + for (const auto& [_, object] : mObjects) { + if (auto* casted = std::any_cast(&object); casted != nullptr) { + result.push_back(*casted); + } + } + return result; + } + + template Pred> + std::vector getAllOfTypeIf(Pred filter) const + { + std::vector result; + for (const auto& [_, object] : mObjects) { + if (auto* casted = std::any_cast(&object); casted != nullptr) { + if (filter(*casted)) { + result.push_back(*casted); + } + } + } + return result; + } + + private: + std::map> mObjects; +}; + +} // namespace o2::quality_control::core diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 36d01d6a2f..5ed51804f5 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -17,6 +17,7 @@ #include "QualityControl/CheckInterface.h" #include "QualityControl/ReferenceUtils.h" #include "QualityControl/MonitorObject.h" +#include "QualityControl/Data.h" using namespace std; using namespace o2::quality_control::core; @@ -24,6 +25,31 @@ using namespace o2::quality_control::core; namespace o2::quality_control::checker { +core::Quality CheckInterface::check(std::map>* moMap) +{ + Data data(*moMap); + return check(data); +}; + +core::Quality CheckInterface::check(const core::Data& data) +{ + return core::Quality{}; +}; + +std::string CheckInterface::getAcceptedType() { return "TObject"; } + +bool CheckInterface::isObjectCheckable(const std::shared_ptr mo) +{ + return isObjectCheckable(mo.get()); +} + +bool CheckInterface::isObjectCheckable(const MonitorObject* mo) +{ + TObject* encapsulated = mo->getObject(); + + return encapsulated->IsA()->InheritsFrom(getAcceptedType().c_str()); +} + void CheckInterface::configure() { // noop, override it if you want. diff --git a/Framework/src/Data.cxx b/Framework/src/Data.cxx new file mode 100644 index 0000000000..ba9208e21e --- /dev/null +++ b/Framework/src/Data.cxx @@ -0,0 +1,27 @@ +// Copyright 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 Data.h +/// \author Michal Tichak +/// + +#include "QualityControl/Data.h" + +namespace o2::quality_control::core +{ +Data::Data(const std::map>& moMap) +{ + for (const auto& [key, mo] : moMap) { + insert(key, mo); + } +} +} // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx new file mode 100644 index 0000000000..ac5359796a --- /dev/null +++ b/Framework/test/testData.cxx @@ -0,0 +1,79 @@ +// Copyright 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 testData.cxx +/// \author Michal Tichak +/// + +#include +#include "QualityControl/Data.h" + +using namespace o2::quality_control::core; + +TEST_CASE("Data constructor", "[Data]") +{ + REQUIRE_NOTHROW([]() { Data data{}; }); +} + +TEST_CASE("Data insert and get", "[Data]") +{ + Data data; + data.insert("test", 1); + auto valueStr = data.get("test"); + REQUIRE(!valueStr.has_value()); + auto valueInt = data.get("test"); + REQUIRE(valueInt.has_value()); + REQUIRE(valueInt.value() == 1); +} + +TEST_CASE("Data getAllOfType", "[Data]") +{ + Data data; + data.insert("int1", 1); + data.insert("string", std::string{ "1" }); + data.insert("int2", 1); + data.insert("long", 1l); + + const auto ints = data.getAllOfType(); + REQUIRE(ints.size() == 2); + REQUIRE(ints[0] == 1); + REQUIRE(ints[1] == 1); + + const auto strings = data.getAllOfType(); + REQUIRE(strings.size() == 1); + REQUIRE(strings[0] == "1"); + + const auto longs = data.getAllOfType(); + REQUIRE(longs.size() == 1); + REQUIRE(longs[0] == 1u); + + struct nonexistent { + }; + + const auto nonexistens = data.getAllOfType(); + REQUIRE(nonexistens.empty()); +} + +template +struct named { + std::string name; + T val; +}; + +TEST_CASE("Data getAllOfTypeIf", "[Data]") +{ + Data data; + data.insert("1", named{ "1", 4 }); + data.insert("1", named{ "1", 4l }); + auto filtered = data.getAllOfTypeIf>([](const auto& val) { return val.name == "1"; }); + REQUIRE(filtered.size() == 1); +} diff --git a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h index 660984d72c..a2cad2e46c 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h @@ -35,6 +35,7 @@ class SkeletonCheck : public o2::quality_control::checker::CheckInterface // Override interface void configure() override; Quality check(std::map>* moMap) override; + Quality check(const quality_control::core::Data& data) override; void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; void reset() override; void startOfActivity(const Activity& activity) override; diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index f5deb755ca..fe6514b2ab 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -18,11 +18,14 @@ #include "QualityControl/MonitorObject.h" #include "QualityControl/Quality.h" #include "QualityControl/QcInfoLogger.h" +#include "Skeleton/SkeletonTask.h" +#include "QualityControl/Data.h" // ROOT #include #include #include +#include using namespace std; using namespace o2::quality_control; @@ -40,6 +43,12 @@ void SkeletonCheck::configure() } Quality SkeletonCheck::check(std::map>* moMap) +{ + Data data{ *moMap }; + return check(data); +} + +Quality SkeletonCheck::check(const quality_control::core::Data& data) { // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. Quality result = Quality::Null; @@ -49,35 +58,35 @@ Quality SkeletonCheck::check(std::mapgetName() == "example") { - auto* h = dynamic_cast(mo->getObject()); - if (h == nullptr) { - ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; - continue; - } - // unless we find issues, we assume the quality is good - result = Quality::Good; - - // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. - for (int i = 0; i < h->GetNbinsX(); i++) { - if (i > 0 && i < 8 && h->GetBinContent(i) == 0) { - result = Quality::Bad; - // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. - result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); - break; - } else if ((i == 0 || i > 7) && h->GetBinContent(i) > 0) { - result = Quality::Medium; - // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. - result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); - result.addFlag(FlagTypeFactory::BadTracking(), "We can assign more than one Flag to a Quality"); - } + auto MOs = data.getAllOfTypeIf>([](const auto& mo) { return mo->getName() == "example"; }); + + for (const auto& mo : MOs) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; + continue; + } + // unless we find issues, we assume the quality is good + result = Quality::Good; + + // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. + for (int i = 0; i < h->GetNbinsX(); i++) { + if (i > 0 && i < 8 && h->GetBinContent(i) == 0) { + result = Quality::Bad; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); + break; + } else if ((i == 0 || i > 7) && h->GetBinContent(i) > 0) { + result = Quality::Medium; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); + result.addFlag(FlagTypeFactory::BadTracking(), "We can assign more than one Flag to a Quality"); } - // optionally, we can associate some custom metadata to a Quality - result.addMetadata("mykey", "myvalue"); } + // optionally, we can associate some custom metadata to a Quality + result.addMetadata("mykey", "myvalue"); } + return result; } From 2d2c61d876ccbc636df64415382216027584791d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Fri, 1 Aug 2025 14:07:40 +0200 Subject: [PATCH 02/12] iterators in the middle of work --- Framework/include/QualityControl/Data.h | 92 ++++++++++++++++++++++++- Framework/test/testData.cxx | 25 +++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index da9ddb8c38..6c1afe1666 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -14,9 +14,14 @@ /// \author Michal Tichak /// +#ifndef QC_CORE_DATA_H +#define QC_CORE_DATA_H + #include #include +#include #include +#include #include #include #include @@ -31,7 +36,77 @@ class MonitorObject; class Data { + // change this for boost::flat_map? or do a magic and split by different keys? + using InternalContainer = std::map>; + using InternalContainerConstIterator = InternalContainer::const_iterator; + public: + template + class Iterator + { + public: + using value_type = std::pair, std::reference_wrapper>; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + using reference = const value_type&; + using pointer = const value_type*; + + Iterator() = default; + Iterator(InternalContainerConstIterator it, InternalContainerConstIterator end) + : mIt{ it }, mEnd{ end }, val{ std::nullopt } + { + seek_next_valid(mIt); + } + + reference operator*() const + { + return val.value(); + } + + pointer operator->() const + { + return &val.value(); + } + + Iterator& operator++() + { + // can this be optimised out? + if (mIt != mEnd) { + seek_next_valid(++mIt); + } + return *this; + } + + Iterator operator++(int) + { + auto tmp = *this; + ++*this; + return tmp; + } + + bool operator==(const Iterator& other) const + { + return mIt == other.mIt; + } + + private: + InternalContainerConstIterator mIt; + InternalContainerConstIterator mEnd; + std::optional val; + + void seek_next_valid(InternalContainerConstIterator startingIt) + { + for (auto it = startingIt; it != mEnd; ++it) { + if (auto* casted = std::any_cast(&it->second); casted != nullptr) { + val.emplace(it->first, *casted); + break; + } + } + } + }; + + static_assert(std::forward_iterator>); + Data() = default; Data(const std::map>& moMap); @@ -49,7 +124,6 @@ class Data template void insert(std::string_view key, const StoreType& value) { - // std::cout << "inserting value: " << value << ", of type: " << typeid(StoreType).name() << "\n"; mObjects.insert({ std::string{ key }, value }); } @@ -79,8 +153,22 @@ class Data return result; } + template + Iterator begin() + { + return { mObjects.begin(), mObjects.end() }; + } + + template + Iterator end() + { + return { mObjects.end(), mObjects.end() }; + } + private: - std::map> mObjects; + InternalContainer mObjects; }; } // namespace o2::quality_control::core + +#endif diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index ac5359796a..f8d17007b9 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -77,3 +77,28 @@ TEST_CASE("Data getAllOfTypeIf", "[Data]") auto filtered = data.getAllOfTypeIf>([](const auto& val) { return val.name == "1"; }); REQUIRE(filtered.size() == 1); } + +TEST_CASE("Data iterator", "[Data]") +{ + Data data; + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); + data.insert("testint2", 2); + data.insert("teststr2", std::string{ "2" }); + + auto intIt = data.begin(); + REQUIRE(intIt != data.end()); + REQUIRE(intIt->first.get() == "testint1"); + REQUIRE(intIt->second.get() == 1); + intIt++; + REQUIRE(intIt->first.get() == "testint2"); + REQUIRE(intIt->second.get() == 2); + + auto strIt = data.begin(); + REQUIRE(strIt != data.end()); + REQUIRE(strIt->first.get() == "teststr1"); + REQUIRE(strIt->second.get() == "1"); + ++strIt; + REQUIRE(strIt->first.get() == "teststr2"); + REQUIRE(strIt->second.get() == "2"); +} From 2b03079ee02103b540ea3713f835ab208e7a92e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Tue, 5 Aug 2025 09:52:10 +0200 Subject: [PATCH 03/12] filtering iterators working --- Framework/include/QualityControl/Data.h | 134 +++++++++++++++----- Framework/src/Data.cxx | 6 + Framework/test/testData.cxx | 162 +++++++++++++++++------- Modules/Skeleton/src/SkeletonCheck.cxx | 6 +- 4 files changed, 223 insertions(+), 85 deletions(-) diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index 6c1afe1666..82004887ed 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -20,14 +20,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include namespace o2::quality_control::core { @@ -40,8 +38,38 @@ class Data using InternalContainer = std::map>; using InternalContainerConstIterator = InternalContainer::const_iterator; - public: template + using value_type = std::pair, std::reference_wrapper>; + + template + struct NoPredicateIncrementPolicy { + bool increment(const InternalContainerConstIterator& it, std::optional>& val) + { + if (auto* casted = std::any_cast(&it->second); casted != nullptr) { + val.emplace(it->first, *casted); + return true; + } + return false; + } + }; + + template Pred> + struct PredicateIncrementPolicy { + Pred filter; + bool increment(const InternalContainerConstIterator& it, std::optional>& val) + { + if (auto* casted = std::any_cast(&it->second); casted != nullptr) { + if (filter(*casted)) { + val.emplace(it->first, *casted); + return true; + } + } + return false; + } + }; + + public: + template > class Iterator { public: @@ -51,26 +79,28 @@ class Data using reference = const value_type&; using pointer = const value_type*; + private: + public: Iterator() = default; - Iterator(InternalContainerConstIterator it, InternalContainerConstIterator end) - : mIt{ it }, mEnd{ end }, val{ std::nullopt } + Iterator(const InternalContainerConstIterator& it, const InternalContainerConstIterator& end, const IncrementalPolicy& policy = {}) + : mIt{ it }, mEnd{ end }, mVal{ std::nullopt } { seek_next_valid(mIt); } reference operator*() const { - return val.value(); + return mVal.value(); } pointer operator->() const { - return &val.value(); + return &mVal.value(); } Iterator& operator++() { - // can this be optimised out? + // TODO: can this check be optimised out? if (mIt != mEnd) { seek_next_valid(++mIt); } @@ -92,19 +122,22 @@ class Data private: InternalContainerConstIterator mIt; InternalContainerConstIterator mEnd; - std::optional val; + IncrementalPolicy mPolicy; + std::optional mVal; - void seek_next_valid(InternalContainerConstIterator startingIt) + void seek_next_valid(InternalContainerConstIterator& startingIt) { - for (auto it = startingIt; it != mEnd; ++it) { - if (auto* casted = std::any_cast(&it->second); casted != nullptr) { - val.emplace(it->first, *casted); + for (; startingIt != mEnd; ++startingIt) { + if (mPolicy.increment(startingIt, mVal)) { break; } } } }; + template P> + using FilteringIterator = Iterator>; + static_assert(std::forward_iterator>); Data() = default; @@ -127,44 +160,75 @@ class Data mObjects.insert({ std::string{ key }, value }); } - template - std::vector getAllOfType() const + template + struct Range { + Iterator begin_it; + Iterator end_it; + Iterator begin() { return begin_it; } + Iterator end() { return end_it; } + }; + + template + Range iterate() const noexcept { - std::vector result; - for (const auto& [_, object] : mObjects) { - if (auto* casted = std::any_cast(&object); casted != nullptr) { - result.push_back(*casted); - } - } - return result; + return { + .begin_it = begin(), + .end_it = end() + }; } - template Pred> - std::vector getAllOfTypeIf(Pred filter) const + template P> + struct FilteredRange { + FilteringIterator begin_it; + FilteringIterator end_it; + FilteringIterator begin() { return begin_it; } + FilteringIterator end() { return end_it; } + }; + + template Pred> + FilteredRange iterateAndFilter(Pred&& filter) const noexcept { - std::vector result; - for (const auto& [_, object] : mObjects) { - if (auto* casted = std::any_cast(&object); casted != nullptr) { - if (filter(*casted)) { - result.push_back(*casted); - } - } - } - return result; + return FilteredRange{ + .begin_it = begin(std::forward(filter)), + .end_it = end(std::forward(filter)) + }; } + size_t size(); + template - Iterator begin() + Iterator begin() const noexcept { return { mObjects.begin(), mObjects.end() }; } + template P> + FilteringIterator begin(P&& filter) const noexcept + { + return { mObjects.begin(), mObjects.end(), PredicateIncrementPolicy(filter) }; + } + template - Iterator end() + Iterator end() const noexcept { return { mObjects.end(), mObjects.end() }; } + template P> + FilteringIterator end(P&& filter) const noexcept + { + return { mObjects.end(), mObjects.end(), PredicateIncrementPolicy(filter) }; + } + + template + static std::optional downcast(const Base* base) + { + if (const auto* casted = dynamic_cast(base); casted != nullptr) { + return { casted }; + } + return std::nullopt; + } + private: InternalContainer mObjects; }; diff --git a/Framework/src/Data.cxx b/Framework/src/Data.cxx index ba9208e21e..d82a48b263 100644 --- a/Framework/src/Data.cxx +++ b/Framework/src/Data.cxx @@ -24,4 +24,10 @@ Data::Data(const std::map>& moMap) insert(key, mo); } } + +size_t Data::size() +{ + return mObjects.size(); +} + } // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index f8d17007b9..ef101fecb9 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -19,6 +19,9 @@ using namespace o2::quality_control::core; +struct nonexistent { +}; + TEST_CASE("Data constructor", "[Data]") { REQUIRE_NOTHROW([]() { Data data{}; }); @@ -35,50 +38,123 @@ TEST_CASE("Data insert and get", "[Data]") REQUIRE(valueInt.value() == 1); } -TEST_CASE("Data getAllOfType", "[Data]") +TEST_CASE("Data iterator - constructing", "[Data]") { Data data; - data.insert("int1", 1); - data.insert("string", std::string{ "1" }); - data.insert("int2", 1); - data.insert("long", 1l); - - const auto ints = data.getAllOfType(); - REQUIRE(ints.size() == 2); - REQUIRE(ints[0] == 1); - REQUIRE(ints[1] == 1); + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); - const auto strings = data.getAllOfType(); - REQUIRE(strings.size() == 1); - REQUIRE(strings[0] == "1"); + REQUIRE(data.size() == 2); - const auto longs = data.getAllOfType(); - REQUIRE(longs.size() == 1); - REQUIRE(longs[0] == 1u); + SECTION("int iterator has some value") + { + REQUIRE(data.begin() != data.end()); + } - struct nonexistent { - }; + SECTION("string iterator has some value") + { + REQUIRE(data.begin() != data.end()); + } - const auto nonexistens = data.getAllOfType(); - REQUIRE(nonexistens.empty()); + SECTION("nonexistent value return end iterator") + { + REQUIRE(data.begin() == data.end()); + } } -template -struct named { - std::string name; - T val; -}; +TEST_CASE("Data iterator - incrementing", "[Data]") +{ + Data data; + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); + data.insert("testint2", 2); + data.insert("teststr2", std::string{ "2" }); -TEST_CASE("Data getAllOfTypeIf", "[Data]") + REQUIRE(data.size() == 4); + + SECTION("postfix int iterator") + { + auto intIt = data.begin(); + REQUIRE(intIt != data.end()); + REQUIRE(intIt->first.get() == "testint1"); + REQUIRE(intIt->second.get() == 1); + intIt++; + REQUIRE(intIt != data.end()); + REQUIRE(intIt->first.get() == "testint2"); + REQUIRE(intIt->second.get() == 2); + } + + SECTION("prefix int iterator") + { + auto intIt = data.begin(); + REQUIRE(intIt != data.end()); + REQUIRE(intIt->first.get() == "testint1"); + REQUIRE(intIt->second.get() == 1); + ++intIt; + REQUIRE(intIt != data.end()); + REQUIRE(intIt->first.get() == "testint2"); + REQUIRE(intIt->second.get() == 2); + } + + SECTION("postfix str iterator") + { + auto strIt = data.begin(); + REQUIRE(strIt != data.end()); + REQUIRE(strIt->first.get() == "teststr1"); + REQUIRE(strIt->second.get() == "1"); + strIt++; + REQUIRE(strIt != data.end()); + REQUIRE(strIt->first.get() == "teststr2"); + REQUIRE(strIt->second.get() == "2"); + } + + SECTION("prefix str iterator") + { + auto strIt = data.begin(); + REQUIRE(strIt != data.end()); + REQUIRE(strIt->first.get() == "teststr1"); + REQUIRE(strIt->second.get() == "1"); + ++strIt; + REQUIRE(strIt != data.end()); + REQUIRE(strIt->first.get() == "teststr2"); + REQUIRE(strIt->second.get() == "2"); + } +} + +TEST_CASE("Data iterator - for loop no filtering", "[Data]") { Data data; - data.insert("1", named{ "1", 4 }); - data.insert("1", named{ "1", 4l }); - auto filtered = data.getAllOfTypeIf>([](const auto& val) { return val.name == "1"; }); - REQUIRE(filtered.size() == 1); + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); + data.insert("testint2", 2); + data.insert("teststr2", std::string{ "2" }); + + REQUIRE(data.size() == 4); + + SECTION("int loop") + { + for (const auto& [key, value] : data.iterate()) { + REQUIRE((key.get() == "testint1" || key.get() == "testint2")); + REQUIRE((value.get() == 1 || value.get() == 2)); + } + } + + SECTION("string loop") + { + for (const auto& [key, value] : data.iterate()) { + REQUIRE((key.get() == "teststr1" || key.get() == "teststr2")); + REQUIRE((value.get() == "1" || value.get() == "2")); + } + } + + SECTION("empty loop") + { + auto filteredRange = data.iterate(); + REQUIRE(filteredRange.begin() == filteredRange.end()); + } } -TEST_CASE("Data iterator", "[Data]") +TEST_CASE("Data iterator - for loop filter", "[Data]") { Data data; data.insert("testint1", 1); @@ -86,19 +162,13 @@ TEST_CASE("Data iterator", "[Data]") data.insert("testint2", 2); data.insert("teststr2", std::string{ "2" }); - auto intIt = data.begin(); - REQUIRE(intIt != data.end()); - REQUIRE(intIt->first.get() == "testint1"); - REQUIRE(intIt->second.get() == 1); - intIt++; - REQUIRE(intIt->first.get() == "testint2"); - REQUIRE(intIt->second.get() == 2); - - auto strIt = data.begin(); - REQUIRE(strIt != data.end()); - REQUIRE(strIt->first.get() == "teststr1"); - REQUIRE(strIt->second.get() == "1"); - ++strIt; - REQUIRE(strIt->first.get() == "teststr2"); - REQUIRE(strIt->second.get() == "2"); + REQUIRE(data.size() == 4); + + SECTION("int loop") + { + for (const auto& [key, value] : data.iterateAndFilter([](const int& val) { return val == 1; })) { + REQUIRE((key.get() == "testint1")); + REQUIRE((value.get() == 1)); + } + } } diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index fe6514b2ab..dd24a65f6a 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -58,10 +58,8 @@ Quality SkeletonCheck::check(const quality_control::core::Data& data) // and you can get your custom parameters: ILOG(Debug, Devel) << "custom param physics.pp.myOwnKey1 : " << mCustomParameters.atOrDefaultValue("myOwnKey1", "default_value", "physics", "pp") << ENDM; - auto MOs = data.getAllOfTypeIf>([](const auto& mo) { return mo->getName() == "example"; }); - - for (const auto& mo : MOs) { - auto* h = dynamic_cast(mo->getObject()); + for (const auto& [n, mo] : data.iterateAndFilter>([](const auto& mo) { return mo->getName() == "example"; })) { + auto* h = dynamic_cast(mo.get()->getObject()); if (h == nullptr) { ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; continue; From 1589aa7353f21bbbcf2d181d779ba11968fa5ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Thu, 7 Aug 2025 19:29:07 +0200 Subject: [PATCH 04/12] version 2 --- Framework/CMakeLists.txt | 2 +- Framework/include/QualityControl/Data.h | 205 ++++-------------- .../include/QualityControl/DataAdapters.h | 38 ++++ Framework/src/CheckInterface.cxx | 3 +- Framework/src/{Data.cxx => DataAdapters.cxx} | 16 +- Framework/test/testData.cxx | 180 +++++++-------- Modules/Skeleton/src/SkeletonCheck.cxx | 17 +- 7 files changed, 175 insertions(+), 286 deletions(-) create mode 100644 Framework/include/QualityControl/DataAdapters.h rename Framework/src/{Data.cxx => DataAdapters.cxx} (77%) diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index c7893e4b5a..8ec95bf331 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -137,7 +137,7 @@ add_library(O2QualityControl src/KafkaPoller.cxx src/FlagHelpers.cxx src/ObjectMetadataHelpers.cxx - src/Data.cxx + src/DataAdapters.cxx ) target_include_directories( diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index 82004887ed..36c24ae34c 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -20,128 +20,23 @@ #include #include #include -#include #include #include #include #include -#include +#include +#include namespace o2::quality_control::core { -class MonitorObject; +template +concept invocable_r = std::invocable && std::same_as, Result>; class Data { - // change this for boost::flat_map? or do a magic and split by different keys? - using InternalContainer = std::map>; - using InternalContainerConstIterator = InternalContainer::const_iterator; - - template - using value_type = std::pair, std::reference_wrapper>; - - template - struct NoPredicateIncrementPolicy { - bool increment(const InternalContainerConstIterator& it, std::optional>& val) - { - if (auto* casted = std::any_cast(&it->second); casted != nullptr) { - val.emplace(it->first, *casted); - return true; - } - return false; - } - }; - - template Pred> - struct PredicateIncrementPolicy { - Pred filter; - bool increment(const InternalContainerConstIterator& it, std::optional>& val) - { - if (auto* casted = std::any_cast(&it->second); casted != nullptr) { - if (filter(*casted)) { - val.emplace(it->first, *casted); - return true; - } - } - return false; - } - }; - public: - template > - class Iterator - { - public: - using value_type = std::pair, std::reference_wrapper>; - using difference_type = std::ptrdiff_t; - using iterator_category = std::forward_iterator_tag; - using reference = const value_type&; - using pointer = const value_type*; - - private: - public: - Iterator() = default; - Iterator(const InternalContainerConstIterator& it, const InternalContainerConstIterator& end, const IncrementalPolicy& policy = {}) - : mIt{ it }, mEnd{ end }, mVal{ std::nullopt } - { - seek_next_valid(mIt); - } - - reference operator*() const - { - return mVal.value(); - } - - pointer operator->() const - { - return &mVal.value(); - } - - Iterator& operator++() - { - // TODO: can this check be optimised out? - if (mIt != mEnd) { - seek_next_valid(++mIt); - } - return *this; - } - - Iterator operator++(int) - { - auto tmp = *this; - ++*this; - return tmp; - } - - bool operator==(const Iterator& other) const - { - return mIt == other.mIt; - } - - private: - InternalContainerConstIterator mIt; - InternalContainerConstIterator mEnd; - IncrementalPolicy mPolicy; - std::optional mVal; - - void seek_next_valid(InternalContainerConstIterator& startingIt) - { - for (; startingIt != mEnd; ++startingIt) { - if (mPolicy.increment(startingIt, mVal)) { - break; - } - } - } - }; - - template P> - using FilteringIterator = Iterator>; - - static_assert(std::forward_iterator>); - Data() = default; - Data(const std::map>& moMap); template std::optional get(std::string_view key) @@ -154,83 +49,73 @@ class Data return std::nullopt; } - template - void insert(std::string_view key, const StoreType& value) + template + void emplace(std::string_view key, Args&&... args) { - mObjects.insert({ std::string{ key }, value }); + mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); } template - struct Range { - Iterator begin_it; - Iterator end_it; - Iterator begin() { return begin_it; } - Iterator end() { return end_it; } - }; + void insert(std::string_view key, const T& value) + { + mObjects.insert({ std::string{ key }, value }); + } template - Range iterate() const noexcept + static const T* any_cast_try_shared_ptr(const std::any& value) { - return { - .begin_it = begin(), - .end_it = end() - }; + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } else { + return std::any_cast(&value); + } } - template P> - struct FilteredRange { - FilteringIterator begin_it; - FilteringIterator end_it; - FilteringIterator begin() { return begin_it; } - FilteringIterator end() { return end_it; } - }; + template + static constexpr auto any_to_specific = std::views::transform([](const auto& pair) -> std::pair { return { pair.first, any_cast_try_shared_ptr(pair.second) }; }); + + static constexpr auto filter_nullptr_in_pair = std::views::filter([](const auto& pair) { return pair.second != nullptr; }); - template Pred> - FilteredRange iterateAndFilter(Pred&& filter) const noexcept - { - return FilteredRange{ - .begin_it = begin(std::forward(filter)), - .end_it = end(std::forward(filter)) - }; - } + static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); - size_t size(); + static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); - template - Iterator begin() const noexcept - { - return { mObjects.begin(), mObjects.end() }; - } + static constexpr auto pair_to_value = std::views::transform([](const auto& pair) { return pair.second; }); - template P> - FilteringIterator begin(P&& filter) const noexcept + static constexpr auto pointer_to_reference = std::views::transform([](const auto* ptr) -> auto& { return *ptr; }); + + template + auto iterateByType() const { - return { mObjects.begin(), mObjects.end(), PredicateIncrementPolicy(filter) }; + return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_reference; } - template - Iterator end() const noexcept + template &> Pred> + auto iterateByTypeAndFilter(Pred&& filter) const { - return { mObjects.end(), mObjects.end() }; + return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_reference; } - template P> - FilteringIterator end(P&& filter) const noexcept + template &> Pred, invocable_r Transform> + auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const { - return { mObjects.end(), mObjects.end(), PredicateIncrementPolicy(filter) }; + return mObjects | + any_to_specific | + filter_nullptr_in_pair | + std::views::filter(filter) | + pair_to_value | + std::views::transform(transform) | + filter_nullptr | + pointer_to_reference; } - template - static std::optional downcast(const Base* base) + size_t size() const noexcept { - if (const auto* casted = dynamic_cast(base); casted != nullptr) { - return { casted }; - } - return std::nullopt; + return mObjects.size(); } private: - InternalContainer mObjects; + std::map> mObjects; }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h new file mode 100644 index 0000000000..ceac33c80f --- /dev/null +++ b/Framework/include/QualityControl/DataAdapters.h @@ -0,0 +1,38 @@ +// Copyright 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 DataAdapters.h +/// \author Michal Tichak +/// + +#ifndef QC_CORE_DATA_ADAPTERS_H +#define QC_CORE_DATA_ADAPTERS_H + +#include "Data.h" +#include "QualityControl/MonitorObject.h" + +namespace o2::quality_control::core +{ + +Data createData(const std::map>& moMap); + +template +auto iterateMOsFilterByNameAndTransform(const Data& data, std::string_view moName) +{ + return data.iterateByTypeFilterAndTransform( + [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, + [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); +} + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 5ed51804f5..5aeb5ab0a1 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -18,6 +18,7 @@ #include "QualityControl/ReferenceUtils.h" #include "QualityControl/MonitorObject.h" #include "QualityControl/Data.h" +#include "QualityControl/DataAdapters.h" using namespace std; using namespace o2::quality_control::core; @@ -27,7 +28,7 @@ namespace o2::quality_control::checker core::Quality CheckInterface::check(std::map>* moMap) { - Data data(*moMap); + auto data = createData(*moMap); return check(data); }; diff --git a/Framework/src/Data.cxx b/Framework/src/DataAdapters.cxx similarity index 77% rename from Framework/src/Data.cxx rename to Framework/src/DataAdapters.cxx index d82a48b263..7711550506 100644 --- a/Framework/src/Data.cxx +++ b/Framework/src/DataAdapters.cxx @@ -10,24 +10,22 @@ // or submit itself to any jurisdiction. /// -/// \file Data.h +/// \file DataAdapters.cxx /// \author Michal Tichak /// -#include "QualityControl/Data.h" +#include "QualityControl/DataAdapters.h" namespace o2::quality_control::core { -Data::Data(const std::map>& moMap) + +Data createData(const std::map>& moMap) { + Data data; for (const auto& [key, mo] : moMap) { - insert(key, mo); + data.insert(key, mo); } -} - -size_t Data::size() -{ - return mObjects.size(); + return data; } } // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index ef101fecb9..b0d288d8d4 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -14,15 +14,21 @@ /// \author Michal Tichak /// +#include #include +#include "Framework/include/QualityControl/MonitorObject.h" #include "QualityControl/Data.h" +#include "QualityControl/DataAdapters.h" +#include +#include +#include using namespace o2::quality_control::core; struct nonexistent { }; -TEST_CASE("Data constructor", "[Data]") +TEST_CASE("Data - constructor", "[Data]") { REQUIRE_NOTHROW([]() { Data data{}; }); } @@ -38,137 +44,103 @@ TEST_CASE("Data insert and get", "[Data]") REQUIRE(valueInt.value() == 1); } -TEST_CASE("Data iterator - constructing", "[Data]") +TEST_CASE("Data - iterateByType", "[Data]") { Data data; data.insert("testint1", 1); data.insert("teststr1", std::string{ "1" }); - REQUIRE(data.size() == 2); - SECTION("int iterator has some value") - { - REQUIRE(data.begin() != data.end()); - } - - SECTION("string iterator has some value") + SECTION("iterate by int") { - REQUIRE(data.begin() != data.end()); + size_t count{}; + for (auto& v : data.iterateByType()) { + REQUIRE(v == 1); + count++; + } + REQUIRE(count == 1); } - SECTION("nonexistent value return end iterator") + SECTION("iterate by nonexistent") { - REQUIRE(data.begin() == data.end()); + size_t count{}; + REQUIRE(data.iterateByType().empty()); } } -TEST_CASE("Data iterator - incrementing", "[Data]") +TEST_CASE("Data - iterateByTypeAndFilter", "[Data]") { Data data; - data.insert("testint1", 1); - data.insert("teststr1", std::string{ "1" }); - data.insert("testint2", 2); - data.insert("teststr2", std::string{ "2" }); - - REQUIRE(data.size() == 4); - - SECTION("postfix int iterator") - { - auto intIt = data.begin(); - REQUIRE(intIt != data.end()); - REQUIRE(intIt->first.get() == "testint1"); - REQUIRE(intIt->second.get() == 1); - intIt++; - REQUIRE(intIt != data.end()); - REQUIRE(intIt->first.get() == "testint2"); - REQUIRE(intIt->second.get() == 2); - } - - SECTION("prefix int iterator") - { - auto intIt = data.begin(); - REQUIRE(intIt != data.end()); - REQUIRE(intIt->first.get() == "testint1"); - REQUIRE(intIt->second.get() == 1); - ++intIt; - REQUIRE(intIt != data.end()); - REQUIRE(intIt->first.get() == "testint2"); - REQUIRE(intIt->second.get() == 2); + data.insert("1", 1); + data.insert("2", 2); + data.insert("str", "str"); + REQUIRE(data.size() == 3); + + size_t count{}; + for (const auto& v : data.iterateByTypeAndFilter([](const auto& pair) -> bool { return *pair.second == 2; })) { + ++count; + REQUIRE(v == 2); } + REQUIRE(count == 1); +} - SECTION("postfix str iterator") - { - auto strIt = data.begin(); - REQUIRE(strIt != data.end()); - REQUIRE(strIt->first.get() == "teststr1"); - REQUIRE(strIt->second.get() == "1"); - strIt++; - REQUIRE(strIt != data.end()); - REQUIRE(strIt->first.get() == "teststr2"); - REQUIRE(strIt->second.get() == "2"); - } +struct Base { + int v; +}; - SECTION("prefix str iterator") - { - auto strIt = data.begin(); - REQUIRE(strIt != data.end()); - REQUIRE(strIt->first.get() == "teststr1"); - REQUIRE(strIt->second.get() == "1"); - ++strIt; - REQUIRE(strIt != data.end()); - REQUIRE(strIt->first.get() == "teststr2"); - REQUIRE(strIt->second.get() == "2"); - } -} +struct Derived : public Base { +}; -TEST_CASE("Data iterator - for loop no filtering", "[Data]") +TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") { - Data data; - data.insert("testint1", 1); - data.insert("teststr1", std::string{ "1" }); - data.insert("testint2", 2); - data.insert("teststr2", std::string{ "2" }); - REQUIRE(data.size() == 4); + auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + std::shared_ptr mo1 = std::make_shared(h1, "taskname", "class1", "TST"); - SECTION("int loop") - { - for (const auto& [key, value] : data.iterate()) { - REQUIRE((key.get() == "testint1" || key.get() == "testint2")); - REQUIRE((value.get() == 1 || value.get() == 2)); - } - } + auto h2 = new TH1F("th12", "th12", 100, 0, 99); + std::shared_ptr mo2 = std::make_shared(h2, "taskname", "class2", "TST"); - SECTION("string loop") - { - for (const auto& [key, value] : data.iterate()) { - REQUIRE((key.get() == "teststr1" || key.get() == "teststr2")); - REQUIRE((value.get() == "1" || value.get() == "2")); - } - } - - SECTION("empty loop") - { - auto filteredRange = data.iterate(); - REQUIRE(filteredRange.begin() == filteredRange.end()); + Data data; + data.insert("1", mo1); + data.insert("2", mo2); + data.insert("str", "str"); + REQUIRE(data.size() == 3); + auto filtered = data.iterateByTypeFilterAndTransform( + [](const auto& pair) -> bool { return std::string_view{ pair.second->GetName() } == "th11"; }, + [](const MonitorObject* ptr) -> const TH1F* { return dynamic_cast(ptr->getObject()); }); + + REQUIRE(!filtered.empty()); + size_t count{}; + for (const auto& th1 : filtered) { + REQUIRE(std::string_view{ th1.GetName() } == "th11"); + ++count; } + REQUIRE(count == 1); } -TEST_CASE("Data iterator - for loop filter", "[Data]") +TEST_CASE("Data - Monitor adaptors", "[Data]") { - Data data; - data.insert("testint1", 1); - data.insert("teststr1", std::string{ "1" }); - data.insert("testint2", 2); - data.insert("teststr2", std::string{ "2" }); + auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + std::shared_ptr mo1 = std::make_shared(h1, "taskname", "class1", "TST"); - REQUIRE(data.size() == 4); + auto* h2 = new TH1F("th12", "th12", 100, 0, 99); + std::shared_ptr mo2 = std::make_shared(h2, "taskname", "class2", "TST"); - SECTION("int loop") - { - for (const auto& [key, value] : data.iterateAndFilter([](const int& val) { return val == 1; })) { - REQUIRE((key.get() == "testint1")); - REQUIRE((value.get() == 1)); - } + std::map> map; + map.emplace(mo1->getFullName(), mo1); + map.emplace(mo2->getFullName(), mo2); + + auto data = createData(map); + + REQUIRE(data.size() == 2); + + auto filteredHistos = iterateMOsFilterByNameAndTransform(data, "th11"); + std::vector result(filteredHistos.begin(), filteredHistos.end()); + // REQUIRE(!filteredHistos.empty()); + size_t count{}; + for (const auto& histo1d : result) { + REQUIRE(std::string_view{ histo1d.GetName() } == "th11"); + ++count; } + REQUIRE(count == 1); } diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index dd24a65f6a..e58a13a229 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -20,12 +20,12 @@ #include "QualityControl/QcInfoLogger.h" #include "Skeleton/SkeletonTask.h" #include "QualityControl/Data.h" +#include "QualityControl/DataAdapters.h" // ROOT #include #include #include -#include using namespace std; using namespace o2::quality_control; @@ -44,7 +44,7 @@ void SkeletonCheck::configure() Quality SkeletonCheck::check(std::map>* moMap) { - Data data{ *moMap }; + auto data = createData(*moMap); return check(data); } @@ -58,23 +58,18 @@ Quality SkeletonCheck::check(const quality_control::core::Data& data) // and you can get your custom parameters: ILOG(Debug, Devel) << "custom param physics.pp.myOwnKey1 : " << mCustomParameters.atOrDefaultValue("myOwnKey1", "default_value", "physics", "pp") << ENDM; - for (const auto& [n, mo] : data.iterateAndFilter>([](const auto& mo) { return mo->getName() == "example"; })) { - auto* h = dynamic_cast(mo.get()->getObject()); - if (h == nullptr) { - ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; - continue; - } + for (const auto& h : iterateMOsFilterByNameAndTransform(data, "example")) { // unless we find issues, we assume the quality is good result = Quality::Good; // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. - for (int i = 0; i < h->GetNbinsX(); i++) { - if (i > 0 && i < 8 && h->GetBinContent(i) == 0) { + for (int i = 0; i < h.GetNbinsX(); i++) { + if (i > 0 && i < 8 && h.GetBinContent(i) == 0) { result = Quality::Bad; // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); break; - } else if ((i == 0 || i > 7) && h->GetBinContent(i) > 0) { + } else if ((i == 0 || i > 7) && h.GetBinContent(i) > 0) { result = Quality::Medium; // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); From 047fdf2968c2f4ec70cfeab1d884b249be9e28b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Fri, 8 Aug 2025 16:33:23 +0200 Subject: [PATCH 05/12] Data used in aggregators --- .../QualityControl/AggregatorInterface.h | 9 +++- Framework/include/QualityControl/Data.h | 14 +++++- .../include/QualityControl/DataAdapters.h | 10 ++++ Framework/src/AggregatorInterface.cxx | 12 +++++ Framework/src/DataAdapters.cxx | 9 ++++ Framework/test/testData.cxx | 47 +++++++++++++++++-- .../include/Skeleton/SkeletonAggregator.h | 5 +- Modules/Skeleton/src/SkeletonAggregator.cxx | 14 +++--- 8 files changed, 103 insertions(+), 17 deletions(-) diff --git a/Framework/include/QualityControl/AggregatorInterface.h b/Framework/include/QualityControl/AggregatorInterface.h index f33ec20171..12fb2feb78 100644 --- a/Framework/include/QualityControl/AggregatorInterface.h +++ b/Framework/include/QualityControl/AggregatorInterface.h @@ -23,6 +23,7 @@ #include "QualityControl/UserCodeInterface.h" #include "QualityControl/Quality.h" #include "QualityControl/Activity.h" +#include "QualityControl/Data.h" namespace o2::quality_control::checker { @@ -42,7 +43,13 @@ class AggregatorInterface : public o2::quality_control::core::UserCodeInterface /// /// @param qoMap A map of the the QualityObjects to aggregate and their full names. /// @return The new qualities, associated with a name. - virtual std::map aggregate(std::map>& qoMap) = 0; + virtual std::map aggregate(std::map>& qoMap); + + /// \brief Returns new qualities (usually fewer) based on the input qualities stored in Data structure + /// + /// @param data A generic data structure containing QualityObjects or possible other inputs. + /// @return The new qualities, associated with a name. + virtual std::map aggregate(const core::Data& data); virtual void startOfActivity(const core::Activity& activity); virtual void endOfActivity(const core::Activity& activity); diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index 36c24ae34c..34e1862cc3 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -64,11 +64,21 @@ class Data template static const T* any_cast_try_shared_ptr(const std::any& value) { + // sadly it is necessary to check for any of these types if we want to test for + // shared_ptr, raw ptr and a value if (auto* casted = std::any_cast>(&value); casted != nullptr) { return casted->get(); - } else { - return std::any_cast(&value); } + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + return std::any_cast(&value); } template diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h index ceac33c80f..3a89dff21c 100644 --- a/Framework/include/QualityControl/DataAdapters.h +++ b/Framework/include/QualityControl/DataAdapters.h @@ -19,11 +19,13 @@ #include "Data.h" #include "QualityControl/MonitorObject.h" +#include "QualityObject.h" namespace o2::quality_control::core { Data createData(const std::map>& moMap); +Data createData(const QualityObjectsMapType& moMap); template auto iterateMOsFilterByNameAndTransform(const Data& data, std::string_view moName) @@ -33,6 +35,14 @@ auto iterateMOsFilterByNameAndTransform(const Data& data, std::string_view moNam [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); } +template +auto iterateQOsFilterByNameAndTransform(const Data& data, std::string_view moName) +{ + return data.iterateByTypeFilterAndTransform( + [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, + [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); +} + } // namespace o2::quality_control::core #endif diff --git a/Framework/src/AggregatorInterface.cxx b/Framework/src/AggregatorInterface.cxx index ed96e5052f..41c19201e1 100644 --- a/Framework/src/AggregatorInterface.cxx +++ b/Framework/src/AggregatorInterface.cxx @@ -15,6 +15,7 @@ /// #include "QualityControl/AggregatorInterface.h" +#include "QualityControl/DataAdapters.h" using namespace std; using namespace o2::quality_control::core; @@ -22,6 +23,17 @@ using namespace o2::quality_control::core; namespace o2::quality_control::checker { +std::map AggregatorInterface::aggregate(std::map>& qoMap) +{ + auto data = createData(qoMap); + return aggregate(data); +} + +std::map AggregatorInterface::aggregate(const core::Data& data) +{ + return {}; +} + void AggregatorInterface::startOfActivity(const Activity& activity) { // noop, override it if you want. diff --git a/Framework/src/DataAdapters.cxx b/Framework/src/DataAdapters.cxx index 7711550506..e846c02278 100644 --- a/Framework/src/DataAdapters.cxx +++ b/Framework/src/DataAdapters.cxx @@ -28,4 +28,13 @@ Data createData(const std::map>& moM return data; } +Data createData(const QualityObjectsMapType& qoMap) +{ + Data data; + for (const auto& [key, qo] : qoMap) { + data.insert(key, qo); + } + return data; +} + } // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index b0d288d8d4..cd0c26f941 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -118,7 +118,7 @@ TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") REQUIRE(count == 1); } -TEST_CASE("Data - Monitor adaptors", "[Data]") +TEST_CASE("Data - Monitor adaptors MOs", "[Data]") { auto* h1 = new TH1F("th11", "th11", 100, 0, 99); std::shared_ptr mo1 = std::make_shared(h1, "taskname", "class1", "TST"); @@ -135,12 +135,51 @@ TEST_CASE("Data - Monitor adaptors", "[Data]") REQUIRE(data.size() == 2); auto filteredHistos = iterateMOsFilterByNameAndTransform(data, "th11"); - std::vector result(filteredHistos.begin(), filteredHistos.end()); - // REQUIRE(!filteredHistos.empty()); + REQUIRE(!filteredHistos.empty()); size_t count{}; - for (const auto& histo1d : result) { + for (const auto& histo1d : filteredHistos) { REQUIRE(std::string_view{ histo1d.GetName() } == "th11"); ++count; } REQUIRE(count == 1); } + +TEST_CASE("Data - Monitor adaptors QOs", "[Data]") +{ + QualityObjectsMapType qoMap; + qoMap["1"] = std::make_shared(Quality::Good, "1"); + qoMap["2"] = std::make_shared(Quality::Good, "2"); + + auto data = createData(qoMap); + + REQUIRE(data.size() == 2); + + auto filteredObjects = data.iterateByType(); + REQUIRE(!filteredObjects.empty()); + size_t count{}; + for (const auto& qo : filteredObjects) { + const auto& name = qo.getName(); + REQUIRE((name == "1" || name == "2")); + ++count; + } + REQUIRE(count == 2); +} + +TEST_CASE("Data - raw pointers", "[Data]") +{ + Data data; + int a = 1; + int b = 2; + data.insert("1", &a); + data.insert("2", &b); + + auto ints = data.iterateByType(); + REQUIRE(!ints.empty()); + + size_t count{}; + for (const auto& v : ints) { + REQUIRE((v == 1 || v == 2)); + ++count; + } + REQUIRE(count == 2); +} diff --git a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h index f42d8af942..c34869eed2 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h @@ -21,6 +21,7 @@ #include // QC #include "QualityControl/AggregatorInterface.h" +#include "QualityControl/Data.h" namespace o2::quality_control_modules::skeleton { @@ -32,11 +33,11 @@ class SkeletonAggregator : public o2::quality_control::checker::AggregatorInterf public: // Override interface void configure() override; - std::map aggregate(o2::quality_control::core::QualityObjectsMapType& qoMap) override; + std::map aggregate(const o2::quality_control::core::Data& data) override; ClassDefOverride(SkeletonAggregator, 1); }; } // namespace o2::quality_control_modules::skeleton -#endif //QUALITYCONTROL_SKELETONAGGREGATOR_H +#endif // QUALITYCONTROL_SKELETONAGGREGATOR_H diff --git a/Modules/Skeleton/src/SkeletonAggregator.cxx b/Modules/Skeleton/src/SkeletonAggregator.cxx index 985957a438..fa31908d69 100644 --- a/Modules/Skeleton/src/SkeletonAggregator.cxx +++ b/Modules/Skeleton/src/SkeletonAggregator.cxx @@ -32,22 +32,20 @@ void SkeletonAggregator::configure() std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey", "fallback value"); } -std::map SkeletonAggregator::aggregate(QualityObjectsMapType& qoMap) +std::map SkeletonAggregator::aggregate(const o2::quality_control::core::Data& data) { // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. std::map result; ILOG(Info, Devel) << "Entered SkeletonAggregator::aggregate" << ENDM; - ILOG(Info, Devel) << " received a list of size : " << qoMap.size() << ENDM; - for (const auto& item : qoMap) { - ILOG(Info, Devel) << "Object: " << (*item.second) << ENDM; - } + ILOG(Info, Devel) << " received a data of size : " << data.size() << ENDM; // we return the worse quality of all the objects we receive Quality current = Quality::Good; - for (const auto& qo : qoMap) { - if (qo.second->getQuality().isWorseThan(current)) { - current = qo.second->getQuality(); + for (const auto& qo : data.iterateByType()) { + ILOG(Info, Devel) << "Object: " << qo << ENDM; + if (qo.getQuality().isWorseThan(current)) { + current = qo.getQuality(); } } From e34ad3984c03938b356638c84c89eb03cca7043c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Mon, 11 Aug 2025 17:13:07 +0200 Subject: [PATCH 06/12] benchmarks --- .../include/QualityControl/CheckInterface.h | 3 +- Framework/include/QualityControl/Data.h | 29 ++++- .../include/QualityControl/DataAdapters.h | 14 +-- Framework/test/testData.cxx | 103 +++++++++++++++++- 4 files changed, 131 insertions(+), 18 deletions(-) diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index 3485fb4be2..94ba3a2c7b 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -22,11 +22,12 @@ #include "QualityControl/UserCodeInterface.h" #include "QualityControl/Activity.h" +#include "QualityControl/Data.h" + namespace o2::quality_control::core { class Activity; class MonitorObject; -class Data; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index 34e1862cc3..e39cf4ff5f 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -33,13 +33,14 @@ namespace o2::quality_control::core template concept invocable_r = std::invocable && std::same_as, Result>; -class Data +template +class DataGeneric { public: - Data() = default; + DataGeneric() = default; template - std::optional get(std::string_view key) + std::optional> get(std::string_view key) { if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { @@ -125,9 +126,29 @@ class Data } private: - std::map> mObjects; + ContainerMap mObjects; }; +struct StringHash { + using is_transparent = void; // Required for heterogeneous lookup + + std::size_t operator()(const std::string& str) const + { + return std::hash{}(str); + } + + std::size_t operator()(std::string_view sv) const + { + return std::hash{}(sv); + } +}; + +using transparent_unordered_map = std::unordered_map>; + +using Data = DataGeneric; + +// using Data = DataGeneric>>; + } // namespace o2::quality_control::core #endif diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h index 3a89dff21c..2cad3082aa 100644 --- a/Framework/include/QualityControl/DataAdapters.h +++ b/Framework/include/QualityControl/DataAdapters.h @@ -27,18 +27,10 @@ namespace o2::quality_control::core Data createData(const std::map>& moMap); Data createData(const QualityObjectsMapType& moMap); -template -auto iterateMOsFilterByNameAndTransform(const Data& data, std::string_view moName) +template +auto iterateMOsFilterByNameAndTransform(const DataGeneric& data, std::string_view moName) { - return data.iterateByTypeFilterAndTransform( - [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, - [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); -} - -template -auto iterateQOsFilterByNameAndTransform(const Data& data, std::string_view moName) -{ - return data.iterateByTypeFilterAndTransform( + return data.template iterateByTypeFilterAndTransform( [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); } diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index cd0c26f941..bc38546400 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -15,13 +15,14 @@ /// #include +#include #include #include "Framework/include/QualityControl/MonitorObject.h" #include "QualityControl/Data.h" #include "QualityControl/DataAdapters.h" #include -#include #include +#include using namespace o2::quality_control::core; @@ -41,7 +42,7 @@ TEST_CASE("Data insert and get", "[Data]") REQUIRE(!valueStr.has_value()); auto valueInt = data.get("test"); REQUIRE(valueInt.has_value()); - REQUIRE(valueInt.value() == 1); + REQUIRE(valueInt == 1); } TEST_CASE("Data - iterateByType", "[Data]") @@ -183,3 +184,101 @@ TEST_CASE("Data - raw pointers", "[Data]") } REQUIRE(count == 2); } + +using stdmap = std::map>; +using boostflatmap = boost::container::flat_map>; + +TEMPLATE_TEST_CASE("Data - inserting fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) +{ + constexpr size_t iterations = 20'000; + + BENCHMARK("insert size_t") + { + DataGeneric data; + // for (size_t i = 0; i != iterations; ++i) { + for (size_t i = iterations; i != 0; --i) { + data.insert(std::to_string(i), i); + } + }; +} + +TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) +{ + constexpr size_t iterations = 20000; + DataGeneric data; + for (size_t i = 0; i != iterations; ++i) { + data.insert(std::to_string(i), i); + } + + REQUIRE(data.size() == iterations); + BENCHMARK("iterate size_t") + { + REQUIRE(data.size() == iterations); + size_t r{}; + size_t count{}; + for (const auto& v : data.template iterateByType()) { + r += v; + count++; + } + REQUIRE(count == iterations); + }; +} + +TEMPLATE_TEST_CASE("Data - get fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) +{ + constexpr size_t iterations = 20000; + DataGeneric data; + for (size_t i = 0; i != iterations; ++i) { + data.insert(std::to_string(i), i); + } + + REQUIRE(data.size() == iterations); + BENCHMARK("iterate size_t") + { + size_t r{}; + size_t count{}; + for (size_t i = 0; i != iterations; ++i) { + auto opt = data.template get(std::to_string(i)); + r += opt.value(); + count++; + } + REQUIRE(count == iterations); + }; +} + +std::string generateRandomString(size_t length) +{ + static constexpr std::string_view CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + thread_local std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution distribution(0, CHARACTERS.length() - 1); + + std::string random_string; + random_string.reserve(length); + for (size_t i = 0; i < length; ++i) { + random_string += CHARACTERS[distribution(generator)]; + } + return random_string; +} + +TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) +{ + constexpr size_t iterations = 1000; + std::vector> MOs; + + for (size_t i = 0; i != iterations; ++i) { + const auto name = generateRandomString(20); + auto* h = new TH1F(name.c_str(), name.c_str(), 100, 0, 99); + std::shared_ptr mo = std::make_shared(h, "taskname", "class1", "TST"); + MOs.push_back(mo); + } + + BENCHMARK("insert - iterate MOs") + { + DataGeneric data; + for (const auto& mo : MOs) { + data.insert(mo->getFullName(), mo); + } + + REQUIRE(iterateMOsFilterByNameAndTransform(data, "notimportantname").empty()); + }; +} From eb0740c9672ccb2745df0bf1264b1f1e7b007b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Wed, 13 Aug 2025 13:57:10 +0200 Subject: [PATCH 07/12] helpers implemented --- Framework/include/QualityControl/Data.h | 1 - .../include/QualityControl/DataAdapters.h | 23 +++- .../include/QualityControl/DataAdapters.inl | 111 +++++++++++++++ Framework/src/DataAdapters.cxx | 11 ++ Framework/test/testData.cxx | 130 ++++++++++-------- Modules/Skeleton/src/SkeletonAggregator.cxx | 3 +- Modules/Skeleton/src/SkeletonCheck.cxx | 45 +++--- 7 files changed, 242 insertions(+), 82 deletions(-) create mode 100644 Framework/include/QualityControl/DataAdapters.inl diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index e39cf4ff5f..b34dad6c59 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h index 2cad3082aa..5f48d3f0fa 100644 --- a/Framework/include/QualityControl/DataAdapters.h +++ b/Framework/include/QualityControl/DataAdapters.h @@ -28,13 +28,24 @@ Data createData(const std::map>& moM Data createData(const QualityObjectsMapType& moMap); template -auto iterateMOsFilterByNameAndTransform(const DataGeneric& data, std::string_view moName) -{ - return data.template iterateByTypeFilterAndTransform( - [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, - [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); -} +auto iterateMonitorObjects(const DataGeneric& data, std::string_view moName); +inline auto iterateMonitorObjects(const Data& data); +inline auto iterateMonitorObjects(const Data& data, std::string_view taskName); + +template +std::optional> getMonitorObject(const Data& data, std::string_view objectName, std::string_view taskName); + +// returns first occurence of MO with given name (possible name clash) +template +std::optional> getMonitorObject(const Data& data, std::string_view objectName); + +inline auto iterateQualityObjects(const Data& data); + +std::optional> getQualityObject(const Data& data, std::string_view checkName); } // namespace o2::quality_control::core +// Templates definitions +#include "QualityControl/DataAdapters.inl" + #endif diff --git a/Framework/include/QualityControl/DataAdapters.inl b/Framework/include/QualityControl/DataAdapters.inl new file mode 100644 index 0000000000..647787d853 --- /dev/null +++ b/Framework/include/QualityControl/DataAdapters.inl @@ -0,0 +1,111 @@ +// Copyright 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 DataAdapters.inl +/// \author Michal Tichak +/// + +#ifndef QC_CORE_DATA_ADAPTERS_IMPL_H +#define QC_CORE_DATA_ADAPTERS_IMPL_H + +#include +#include +#include "QualityControl/Data.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" + +#ifdef QC_CORE_DATA_ADAPTERS_H +#include "QualityControl/DataAdapters.h" +#endif + +namespace o2::quality_control::core +{ + +namespace helpers +{ + +} + +template +auto iterateMonitorObjects(const DataGeneric& data, std::string_view moName) +{ + return data.template iterateByTypeFilterAndTransform( + [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, + [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); +} + +inline auto iterateMonitorObjects(const o2::quality_control::core::Data& data) +{ + return data.iterateByType(); +} + +inline auto iterateMonitorObjects(const Data& data, std::string_view taskName) +{ + const auto filterMOByTaskName = [taskName](const auto& pair) { + return pair.second->getTaskName() == taskName; + }; + + return data.iterateByTypeAndFilter(filterMOByTaskName); +} + +namespace helpers +{ + +template +std::optional> getMonitorObjectCommon(const Data& data, Filter&& filter) +{ + if constexpr (std::same_as) { + for (const auto& mo : data.iterateByTypeAndFilter(filter)) { + return { mo }; + } + } else { + const auto getInternalObject = [](const MonitorObject* ptr) -> const auto* { + return dynamic_cast(ptr->getObject()); + }; + for (const auto& v : data.template iterateByTypeFilterAndTransform(filter, getInternalObject)) { + return { v }; + } + } + return std::nullopt; +} + +}; // namespace helpers + +template +std::optional> getMonitorObject(const Data& data, std::string_view objectName, std::string_view taskName) +{ + + const auto filterMOByNameAndTaskName = [objectName, taskName](const auto& pair) { + return std::tuple{ std::string_view{ pair.second->GetName() }, pair.second->getTaskName() } == std::tuple{ objectName, taskName }; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByNameAndTaskName); +} + +template +std::optional> getMonitorObject(const Data& data, std::string_view objectName) +{ + const auto filterMOByName = [objectName](const auto& pair) { + return std::string_view(pair.second->GetName()) == objectName; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByName); +} + +inline auto iterateQualityObjects(const Data& data) +{ + return data.iterateByType(); +} + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/src/DataAdapters.cxx b/Framework/src/DataAdapters.cxx index e846c02278..12a8459a1a 100644 --- a/Framework/src/DataAdapters.cxx +++ b/Framework/src/DataAdapters.cxx @@ -37,4 +37,15 @@ Data createData(const QualityObjectsMapType& qoMap) return data; } +std::optional> getQualityObject(const Data& data, std::string_view objectName) +{ + const auto filterQOByName = [objectName](const auto& pair) { + return std::string_view(pair.second->GetName()) == objectName; + }; + for (const auto& qo : data.iterateByTypeAndFilter(filterQOByName)) { + return { qo }; + } + return std::nullopt; +} + } // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index bc38546400..8b73f08191 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -17,6 +17,7 @@ #include #include #include +#include "Framework/include/QualityControl/DataAdapters.inl" #include "Framework/include/QualityControl/MonitorObject.h" #include "QualityControl/Data.h" #include "QualityControl/DataAdapters.h" @@ -85,13 +86,6 @@ TEST_CASE("Data - iterateByTypeAndFilter", "[Data]") REQUIRE(count == 1); } -struct Base { - int v; -}; - -struct Derived : public Base { -}; - TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") { @@ -119,53 +113,6 @@ TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") REQUIRE(count == 1); } -TEST_CASE("Data - Monitor adaptors MOs", "[Data]") -{ - auto* h1 = new TH1F("th11", "th11", 100, 0, 99); - std::shared_ptr mo1 = std::make_shared(h1, "taskname", "class1", "TST"); - - auto* h2 = new TH1F("th12", "th12", 100, 0, 99); - std::shared_ptr mo2 = std::make_shared(h2, "taskname", "class2", "TST"); - - std::map> map; - map.emplace(mo1->getFullName(), mo1); - map.emplace(mo2->getFullName(), mo2); - - auto data = createData(map); - - REQUIRE(data.size() == 2); - - auto filteredHistos = iterateMOsFilterByNameAndTransform(data, "th11"); - REQUIRE(!filteredHistos.empty()); - size_t count{}; - for (const auto& histo1d : filteredHistos) { - REQUIRE(std::string_view{ histo1d.GetName() } == "th11"); - ++count; - } - REQUIRE(count == 1); -} - -TEST_CASE("Data - Monitor adaptors QOs", "[Data]") -{ - QualityObjectsMapType qoMap; - qoMap["1"] = std::make_shared(Quality::Good, "1"); - qoMap["2"] = std::make_shared(Quality::Good, "2"); - - auto data = createData(qoMap); - - REQUIRE(data.size() == 2); - - auto filteredObjects = data.iterateByType(); - REQUIRE(!filteredObjects.empty()); - size_t count{}; - for (const auto& qo : filteredObjects) { - const auto& name = qo.getName(); - REQUIRE((name == "1" || name == "2")); - ++count; - } - REQUIRE(count == 2); -} - TEST_CASE("Data - raw pointers", "[Data]") { Data data; @@ -279,6 +226,79 @@ TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", st data.insert(mo->getFullName(), mo); } - REQUIRE(iterateMOsFilterByNameAndTransform(data, "notimportantname").empty()); + REQUIRE(iterateMonitorObjects(data, "notimportantname").empty()); }; } + +TEST_CASE("Data adapters - helper functions") +{ + + Data data; + { + for (size_t i{}; i != 10; ++i) { + const auto iStr = std::to_string(i); + const auto thName = std::string("TH1F_") + iStr; + const auto moName = "testMO_" + iStr; + auto* h = new TH1F(thName.c_str(), thName.c_str(), 100, 0, 99); + data.insert(moName, std::make_shared(h, "taskname_" + iStr, "class1", "TST")); + } + + auto* h = new TH1F("TH1F_duplicate", "TH1F_duplicate", 100, 0, 99); + data.insert("testMO_duplicate", std::make_shared(h, "taskname_8", "class1", "TST")); + + data.insert("testQO_1", std::make_shared(Quality::Good, "QO_1")); + data.insert("testQO_2", std::make_shared(Quality::Good, "QO_2")); + } + + REQUIRE(data.size() == 13); + + SECTION("getMonitorObject") + { + const auto moOpt = getMonitorObject(data, "TH1F_1"); + REQUIRE(moOpt.has_value()); + REQUIRE(std::string_view(moOpt.value().get().GetName()) == "TH1F_1"); + const auto th1Opt = getMonitorObject(data, "TH1F_8"); + REQUIRE(th1Opt.has_value()); + REQUIRE(std::string_view(th1Opt.value().get().GetName()) == "TH1F_8"); + + const auto moSpecificOpt = getMonitorObject(data, "TH1F_duplicate", "taskname_8"); + REQUIRE(moSpecificOpt.has_value()); + REQUIRE(moSpecificOpt.value().get().GetName() == std::string_view{ "TH1F_duplicate" }); + REQUIRE(moSpecificOpt.value().get().getTaskName() == std::string_view{ "taskname_8" }); + const auto th1SpecificOpt = getMonitorObject(data, "TH1F_duplicate", "taskname_8"); + REQUIRE(th1SpecificOpt.has_value()); + REQUIRE(th1SpecificOpt.value().get().GetName() == std::string_view{ "TH1F_duplicate" }); + REQUIRE(!getMonitorObject(data, "TH1F_duplicate", "taskname_8").has_value()); + } + + SECTION("iterateMonitorObjects") + { + size_t count{}; + for (auto& mo : iterateMonitorObjects(data)) { + ++count; + } + REQUIRE(count == 11); + + count = 0; + for (auto& mo : iterateMonitorObjects(data, "taskname_8")) { + ++count; + } + REQUIRE(count == 2); + } + + SECTION("getQualityObject") + { + const auto qoOpt = getQualityObject(data, "QO_1"); + REQUIRE(qoOpt.has_value()); + REQUIRE(std::string_view{ qoOpt.value().get().GetName() } == "QO_1"); + } + + SECTION("iterateQualityObjects") + { + size_t count{}; + for (const auto& qo : iterateQualityObjects(data)) { + ++count; + } + REQUIRE(count == 2); + } +} diff --git a/Modules/Skeleton/src/SkeletonAggregator.cxx b/Modules/Skeleton/src/SkeletonAggregator.cxx index fa31908d69..965e242494 100644 --- a/Modules/Skeleton/src/SkeletonAggregator.cxx +++ b/Modules/Skeleton/src/SkeletonAggregator.cxx @@ -16,6 +16,7 @@ #include "Skeleton/SkeletonAggregator.h" #include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DataAdapters.h" using namespace std; using namespace o2::quality_control::core; @@ -42,7 +43,7 @@ std::map SkeletonAggregator::aggregate(const o2::quality_c // we return the worse quality of all the objects we receive Quality current = Quality::Good; - for (const auto& qo : data.iterateByType()) { + for (const auto& qo : iterateQualityObjects(data)) { ILOG(Info, Devel) << "Object: " << qo << ENDM; if (qo.getQuality().isWorseThan(current)) { current = qo.getQuality(); diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index e58a13a229..8a7d873971 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -58,27 +58,34 @@ Quality SkeletonCheck::check(const quality_control::core::Data& data) // and you can get your custom parameters: ILOG(Debug, Devel) << "custom param physics.pp.myOwnKey1 : " << mCustomParameters.atOrDefaultValue("myOwnKey1", "default_value", "physics", "pp") << ENDM; - for (const auto& h : iterateMOsFilterByNameAndTransform(data, "example")) { - // unless we find issues, we assume the quality is good - result = Quality::Good; - - // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. - for (int i = 0; i < h.GetNbinsX(); i++) { - if (i > 0 && i < 8 && h.GetBinContent(i) == 0) { - result = Quality::Bad; - // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. - result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); - break; - } else if ((i == 0 || i > 7) && h.GetBinContent(i) > 0) { - result = Quality::Medium; - // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. - result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); - result.addFlag(FlagTypeFactory::BadTracking(), "We can assign more than one Flag to a Quality"); - } + constexpr static auto name = "example"; + // get MonitorObject with a given name from generic data object and converts it into requested type (TH1 here) + const auto histOpt = getMonitorObject(data, name); + if (!histOpt.has_value()) { + ILOG(Warning, Support) << "Data object does not contain any MonitorObject with a name: " << name << ", or it couldn't be transformed into TH1" << ENDM; + return result; + } + + const TH1& histogram = histOpt.value().get(); + // unless we find issues, we assume the quality is good + result = Quality::Good; + + // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. + for (int i = 0; i < histogram.GetNbinsX(); i++) { + if (i > 0 && i < 8 && histogram.GetBinContent(i) == 0) { + result = Quality::Bad; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); + break; + } else if ((i == 0 || i > 7) && histogram.GetBinContent(i) > 0) { + result = Quality::Medium; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); + result.addFlag(FlagTypeFactory::BadTracking(), "We can assign more than one Flag to a Quality"); } - // optionally, we can associate some custom metadata to a Quality - result.addMetadata("mykey", "myvalue"); } + // optionally, we can associate some custom metadata to a Quality + result.addMetadata("mykey", "myvalue"); return result; } From a2cea350969030392e2fb810638ece762310747d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Wed, 13 Aug 2025 17:22:24 +0200 Subject: [PATCH 08/12] data definition moved to inl file --- Framework/include/QualityControl/Data.h | 84 ++---------- Framework/include/QualityControl/Data.inl | 124 ++++++++++++++++++ .../include/QualityControl/DataAdapters.h | 2 - .../include/QualityControl/DataAdapters.inl | 18 +-- Framework/test/testData.cxx | 13 +- 5 files changed, 145 insertions(+), 96 deletions(-) create mode 100644 Framework/include/QualityControl/Data.inl diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index b34dad6c59..103167bfa0 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -39,90 +39,24 @@ class DataGeneric DataGeneric() = default; template - std::optional> get(std::string_view key) - { - if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { - if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { - return { *casted }; - } - } - return std::nullopt; - } + std::optional> get(std::string_view key); template - void emplace(std::string_view key, Args&&... args) - { - mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); - } - - template - void insert(std::string_view key, const T& value) - { - mObjects.insert({ std::string{ key }, value }); - } + void emplace(std::string_view key, Args&&... args); template - static const T* any_cast_try_shared_ptr(const std::any& value) - { - // sadly it is necessary to check for any of these types if we want to test for - // shared_ptr, raw ptr and a value - if (auto* casted = std::any_cast>(&value); casted != nullptr) { - return casted->get(); - } - if (auto* casted = std::any_cast>(&value); casted != nullptr) { - return casted->get(); - } - if (auto* casted = std::any_cast(&value); casted != nullptr) { - return *casted; - } - if (auto* casted = std::any_cast(&value); casted != nullptr) { - return *casted; - } - return std::any_cast(&value); - } + void insert(std::string_view key, const T& value); template - static constexpr auto any_to_specific = std::views::transform([](const auto& pair) -> std::pair { return { pair.first, any_cast_try_shared_ptr(pair.second) }; }); - - static constexpr auto filter_nullptr_in_pair = std::views::filter([](const auto& pair) { return pair.second != nullptr; }); - - static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); - - static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); - - static constexpr auto pair_to_value = std::views::transform([](const auto& pair) { return pair.second; }); - - static constexpr auto pointer_to_reference = std::views::transform([](const auto* ptr) -> auto& { return *ptr; }); - - template - auto iterateByType() const - { - return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_reference; - } + auto iterateByType() const; template &> Pred> - auto iterateByTypeAndFilter(Pred&& filter) const - { - return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_reference; - } + auto iterateByTypeAndFilter(Pred&& filter) const; template &> Pred, invocable_r Transform> - auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const - { - return mObjects | - any_to_specific | - filter_nullptr_in_pair | - std::views::filter(filter) | - pair_to_value | - std::views::transform(transform) | - filter_nullptr | - pointer_to_reference; - } + auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const; - size_t size() const noexcept - { - return mObjects.size(); - } + size_t size() const noexcept; private: ContainerMap mObjects; @@ -146,8 +80,8 @@ using transparent_unordered_map = std::unordered_map; -// using Data = DataGeneric>>; - } // namespace o2::quality_control::core +#include "Data.inl" + #endif diff --git a/Framework/include/QualityControl/Data.inl b/Framework/include/QualityControl/Data.inl new file mode 100644 index 0000000000..1d79984202 --- /dev/null +++ b/Framework/include/QualityControl/Data.inl @@ -0,0 +1,124 @@ +// Copyright 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 Data.inl +/// \author Michal Tichak +/// + +#include +#include + +namespace o2::quality_control::core +{ + +template +template +std::optional> DataGeneric::get(std::string_view key) +{ + if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { + if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { + return { *casted }; + } + } + return std::nullopt; +} + +template +template +void DataGeneric::emplace(std::string_view key, Args&&... args) +{ + mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); +} + +template +template +void DataGeneric::insert(std::string_view key, const T& value) +{ + mObjects.insert({ std::string{ key }, value }); +} + +namespace internal +{ + +template +static const T* any_cast_try_shared_raw_ptr(const std::any& value) +{ + // sadly it is necessary to check for any of these types if we want to test for + // shared_ptr, raw ptr and a value + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + return std::any_cast(&value); +} + +template +static constexpr auto any_to_specific = std::views::transform([](const auto& pair) -> std::pair { return { pair.first, any_cast_try_shared_raw_ptr(pair.second) }; }); + +static constexpr auto filter_nullptr_in_pair = std::views::filter([](const auto& pair) -> bool { return pair.second != nullptr; }); + +static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); + +static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); + +static constexpr auto pair_to_value = std::views::transform([](const auto& pair) -> const auto* { return pair.second; }); + +static constexpr auto pointer_to_reference = std::views::transform([](const auto* ptr) -> const auto& { return *ptr; }); + +} // namespace internal + +template +template +auto DataGeneric::iterateByType() const +{ + using namespace internal; + return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_reference; +} + +template +template &> Pred> +auto DataGeneric::iterateByTypeAndFilter(Pred&& filter) const +{ + using namespace internal; + return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_reference; +} + +template +template &> Pred, invocable_r Transform> +auto DataGeneric::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const +{ + using namespace internal; + return mObjects | + any_to_specific | + filter_nullptr_in_pair | + std::views::filter(filter) | + pair_to_value | + std::views::transform(transform) | + filter_nullptr | + pointer_to_reference; +} + +template +size_t DataGeneric::size() const noexcept +{ + return mObjects.size(); +} + +} // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h index 5f48d3f0fa..71a1a420f8 100644 --- a/Framework/include/QualityControl/DataAdapters.h +++ b/Framework/include/QualityControl/DataAdapters.h @@ -27,8 +27,6 @@ namespace o2::quality_control::core Data createData(const std::map>& moMap); Data createData(const QualityObjectsMapType& moMap); -template -auto iterateMonitorObjects(const DataGeneric& data, std::string_view moName); inline auto iterateMonitorObjects(const Data& data); inline auto iterateMonitorObjects(const Data& data, std::string_view taskName); diff --git a/Framework/include/QualityControl/DataAdapters.inl b/Framework/include/QualityControl/DataAdapters.inl index 647787d853..fe33e709f5 100644 --- a/Framework/include/QualityControl/DataAdapters.inl +++ b/Framework/include/QualityControl/DataAdapters.inl @@ -30,19 +30,6 @@ namespace o2::quality_control::core { -namespace helpers -{ - -} - -template -auto iterateMonitorObjects(const DataGeneric& data, std::string_view moName) -{ - return data.template iterateByTypeFilterAndTransform( - [name = std::string{ moName }](const std::pair& pair) -> bool { return std::string_view{ pair.second->GetName() } == name; }, - [](const MonitorObject* ptr) -> const Result* { return dynamic_cast(ptr->getObject()); }); -} - inline auto iterateMonitorObjects(const o2::quality_control::core::Data& data) { return data.iterateByType(); @@ -71,19 +58,18 @@ std::optional> getMonitorObjectCommon(c const auto getInternalObject = [](const MonitorObject* ptr) -> const auto* { return dynamic_cast(ptr->getObject()); }; - for (const auto& v : data.template iterateByTypeFilterAndTransform(filter, getInternalObject)) { + for (const auto& v : data.iterateByTypeFilterAndTransform(filter, getInternalObject)) { return { v }; } } return std::nullopt; } -}; // namespace helpers +} // namespace helpers template std::optional> getMonitorObject(const Data& data, std::string_view objectName, std::string_view taskName) { - const auto filterMOByNameAndTaskName = [objectName, taskName](const auto& pair) { return std::tuple{ std::string_view{ pair.second->GetName() }, pair.second->getTaskName() } == std::tuple{ objectName, taskName }; }; diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index 8b73f08191..fa6d125a76 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -149,7 +149,7 @@ TEMPLATE_TEST_CASE("Data - inserting fundamental types", "[.Data-benchmark]", st }; } -TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) +TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) { constexpr size_t iterations = 20000; DataGeneric data; @@ -226,11 +226,18 @@ TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", st data.insert(mo->getFullName(), mo); } - REQUIRE(iterateMonitorObjects(data, "notimportantname").empty()); + const auto filterMOByName = [](const auto& pair) { + return std::string_view(pair.second->GetName()) == "nonexistent"; + }; + + const auto getInternalObject = [](const MonitorObject* ptr) -> const auto* { + return dynamic_cast(ptr->getObject()); + }; + REQUIRE(data.template iterateByTypeFilterAndTransform(filterMOByName, getInternalObject).empty()); }; } -TEST_CASE("Data adapters - helper functions") +TEST_CASE("Data adapters - helper functions", "[Data]") { Data data; From 3a90b19b5af439793ea3103da7ee89dcfb38d1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Wed, 20 Aug 2025 17:02:30 +0200 Subject: [PATCH 09/12] rename Data to QCInputs --- .../QualityControl/AggregatorInterface.h | 2 +- .../include/QualityControl/CheckInterface.h | 2 +- Framework/include/QualityControl/Data.h | 6 ++--- Framework/include/QualityControl/Data.inl | 14 ++++++------ .../include/QualityControl/DataAdapters.h | 16 +++++++------- .../include/QualityControl/DataAdapters.inl | 12 +++++----- Framework/src/AggregatorInterface.cxx | 2 +- Framework/src/CheckInterface.cxx | 2 +- Framework/src/DataAdapters.cxx | 10 ++++----- Framework/test/testData.cxx | 22 +++++++++---------- .../include/Skeleton/SkeletonAggregator.h | 2 +- .../Skeleton/include/Skeleton/SkeletonCheck.h | 2 +- Modules/Skeleton/src/SkeletonAggregator.cxx | 2 +- Modules/Skeleton/src/SkeletonCheck.cxx | 2 +- 14 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Framework/include/QualityControl/AggregatorInterface.h b/Framework/include/QualityControl/AggregatorInterface.h index 12fb2feb78..fd5c13e13a 100644 --- a/Framework/include/QualityControl/AggregatorInterface.h +++ b/Framework/include/QualityControl/AggregatorInterface.h @@ -49,7 +49,7 @@ class AggregatorInterface : public o2::quality_control::core::UserCodeInterface /// /// @param data A generic data structure containing QualityObjects or possible other inputs. /// @return The new qualities, associated with a name. - virtual std::map aggregate(const core::Data& data); + virtual std::map aggregate(const core::QCInputs& data); virtual void startOfActivity(const core::Activity& activity); virtual void endOfActivity(const core::Activity& activity); diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index 94ba3a2c7b..d95334d278 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -57,7 +57,7 @@ class CheckInterface : public core::UserCodeInterface /// /// @param data An object with any type of data possible accesible via full names (i.e. / in case of MOs) as keys. /// @return The quality associated with these objects. - virtual core::Quality check(const core::Data& data); + virtual core::Quality check(const core::QCInputs& data); /// \brief Modify the aspect of the plot. /// diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h index 103167bfa0..b59a60fd0b 100644 --- a/Framework/include/QualityControl/Data.h +++ b/Framework/include/QualityControl/Data.h @@ -33,10 +33,10 @@ template concept invocable_r = std::invocable && std::same_as, Result>; template -class DataGeneric +class QCInputsGeneric { public: - DataGeneric() = default; + QCInputsGeneric() = default; template std::optional> get(std::string_view key); @@ -78,7 +78,7 @@ struct StringHash { using transparent_unordered_map = std::unordered_map>; -using Data = DataGeneric; +using QCInputs = QCInputsGeneric; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/Data.inl b/Framework/include/QualityControl/Data.inl index 1d79984202..157ea8dc23 100644 --- a/Framework/include/QualityControl/Data.inl +++ b/Framework/include/QualityControl/Data.inl @@ -22,7 +22,7 @@ namespace o2::quality_control::core template template -std::optional> DataGeneric::get(std::string_view key) +std::optional> QCInputsGeneric::get(std::string_view key) { if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { @@ -34,14 +34,14 @@ std::optional> DataGeneric::g template template -void DataGeneric::emplace(std::string_view key, Args&&... args) +void QCInputsGeneric::emplace(std::string_view key, Args&&... args) { mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); } template template -void DataGeneric::insert(std::string_view key, const T& value) +void QCInputsGeneric::insert(std::string_view key, const T& value) { mObjects.insert({ std::string{ key }, value }); } @@ -86,7 +86,7 @@ static constexpr auto pointer_to_reference = std::views::transform([](const auto template template -auto DataGeneric::iterateByType() const +auto QCInputsGeneric::iterateByType() const { using namespace internal; return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_reference; @@ -94,7 +94,7 @@ auto DataGeneric::iterateByType() const template template &> Pred> -auto DataGeneric::iterateByTypeAndFilter(Pred&& filter) const +auto QCInputsGeneric::iterateByTypeAndFilter(Pred&& filter) const { using namespace internal; return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_reference; @@ -102,7 +102,7 @@ auto DataGeneric::iterateByTypeAndFilter(Pred&& filter) const template template &> Pred, invocable_r Transform> -auto DataGeneric::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const +auto QCInputsGeneric::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const { using namespace internal; return mObjects | @@ -116,7 +116,7 @@ auto DataGeneric::iterateByTypeFilterAndTransform(Pred&& filter, T } template -size_t DataGeneric::size() const noexcept +size_t QCInputsGeneric::size() const noexcept { return mObjects.size(); } diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h index 71a1a420f8..afb88150a0 100644 --- a/Framework/include/QualityControl/DataAdapters.h +++ b/Framework/include/QualityControl/DataAdapters.h @@ -24,22 +24,22 @@ namespace o2::quality_control::core { -Data createData(const std::map>& moMap); -Data createData(const QualityObjectsMapType& moMap); +QCInputs createData(const std::map>& moMap); +QCInputs createData(const QualityObjectsMapType& moMap); -inline auto iterateMonitorObjects(const Data& data); -inline auto iterateMonitorObjects(const Data& data, std::string_view taskName); +inline auto iterateMonitorObjects(const QCInputs& data); +inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); template -std::optional> getMonitorObject(const Data& data, std::string_view objectName, std::string_view taskName); +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName); // returns first occurence of MO with given name (possible name clash) template -std::optional> getMonitorObject(const Data& data, std::string_view objectName); +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName); -inline auto iterateQualityObjects(const Data& data); +inline auto iterateQualityObjects(const QCInputs& data); -std::optional> getQualityObject(const Data& data, std::string_view checkName); +std::optional> getQualityObject(const QCInputs& data, std::string_view checkName); } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/DataAdapters.inl b/Framework/include/QualityControl/DataAdapters.inl index fe33e709f5..09bc0acf54 100644 --- a/Framework/include/QualityControl/DataAdapters.inl +++ b/Framework/include/QualityControl/DataAdapters.inl @@ -30,12 +30,12 @@ namespace o2::quality_control::core { -inline auto iterateMonitorObjects(const o2::quality_control::core::Data& data) +inline auto iterateMonitorObjects(const o2::quality_control::core::QCInputs& data) { return data.iterateByType(); } -inline auto iterateMonitorObjects(const Data& data, std::string_view taskName) +inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName) { const auto filterMOByTaskName = [taskName](const auto& pair) { return pair.second->getTaskName() == taskName; @@ -48,7 +48,7 @@ namespace helpers { template -std::optional> getMonitorObjectCommon(const Data& data, Filter&& filter) +std::optional> getMonitorObjectCommon(const QCInputs& data, Filter&& filter) { if constexpr (std::same_as) { for (const auto& mo : data.iterateByTypeAndFilter(filter)) { @@ -68,7 +68,7 @@ std::optional> getMonitorObjectCommon(c } // namespace helpers template -std::optional> getMonitorObject(const Data& data, std::string_view objectName, std::string_view taskName) +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName) { const auto filterMOByNameAndTaskName = [objectName, taskName](const auto& pair) { return std::tuple{ std::string_view{ pair.second->GetName() }, pair.second->getTaskName() } == std::tuple{ objectName, taskName }; @@ -78,7 +78,7 @@ std::optional> getMonitorObject(const D } template -std::optional> getMonitorObject(const Data& data, std::string_view objectName) +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName) { const auto filterMOByName = [objectName](const auto& pair) { return std::string_view(pair.second->GetName()) == objectName; @@ -87,7 +87,7 @@ std::optional> getMonitorObject(const D return helpers::getMonitorObjectCommon(data, filterMOByName); } -inline auto iterateQualityObjects(const Data& data) +inline auto iterateQualityObjects(const QCInputs& data) { return data.iterateByType(); } diff --git a/Framework/src/AggregatorInterface.cxx b/Framework/src/AggregatorInterface.cxx index 41c19201e1..6f800bd848 100644 --- a/Framework/src/AggregatorInterface.cxx +++ b/Framework/src/AggregatorInterface.cxx @@ -29,7 +29,7 @@ std::map AggregatorInterface::aggregate(std::map AggregatorInterface::aggregate(const core::Data& data) +std::map AggregatorInterface::aggregate(const core::QCInputs& data) { return {}; } diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 5aeb5ab0a1..123ca3d16d 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -32,7 +32,7 @@ core::Quality CheckInterface::check(std::map>& moMap) +QCInputs createData(const std::map>& moMap) { - Data data; + QCInputs data; for (const auto& [key, mo] : moMap) { data.insert(key, mo); } return data; } -Data createData(const QualityObjectsMapType& qoMap) +QCInputs createData(const QualityObjectsMapType& qoMap) { - Data data; + QCInputs data; for (const auto& [key, qo] : qoMap) { data.insert(key, qo); } return data; } -std::optional> getQualityObject(const Data& data, std::string_view objectName) +std::optional> getQualityObject(const QCInputs& data, std::string_view objectName) { const auto filterQOByName = [objectName](const auto& pair) { return std::string_view(pair.second->GetName()) == objectName; diff --git a/Framework/test/testData.cxx b/Framework/test/testData.cxx index fa6d125a76..73999b5897 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testData.cxx @@ -32,12 +32,12 @@ struct nonexistent { TEST_CASE("Data - constructor", "[Data]") { - REQUIRE_NOTHROW([]() { Data data{}; }); + REQUIRE_NOTHROW([]() { QCInputs data{}; }); } TEST_CASE("Data insert and get", "[Data]") { - Data data; + QCInputs data; data.insert("test", 1); auto valueStr = data.get("test"); REQUIRE(!valueStr.has_value()); @@ -48,7 +48,7 @@ TEST_CASE("Data insert and get", "[Data]") TEST_CASE("Data - iterateByType", "[Data]") { - Data data; + QCInputs data; data.insert("testint1", 1); data.insert("teststr1", std::string{ "1" }); REQUIRE(data.size() == 2); @@ -72,7 +72,7 @@ TEST_CASE("Data - iterateByType", "[Data]") TEST_CASE("Data - iterateByTypeAndFilter", "[Data]") { - Data data; + QCInputs data; data.insert("1", 1); data.insert("2", 2); data.insert("str", "str"); @@ -95,7 +95,7 @@ TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") auto h2 = new TH1F("th12", "th12", 100, 0, 99); std::shared_ptr mo2 = std::make_shared(h2, "taskname", "class2", "TST"); - Data data; + QCInputs data; data.insert("1", mo1); data.insert("2", mo2); data.insert("str", "str"); @@ -115,7 +115,7 @@ TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") TEST_CASE("Data - raw pointers", "[Data]") { - Data data; + QCInputs data; int a = 1; int b = 2; data.insert("1", &a); @@ -141,7 +141,7 @@ TEMPLATE_TEST_CASE("Data - inserting fundamental types", "[.Data-benchmark]", st BENCHMARK("insert size_t") { - DataGeneric data; + QCInputsGeneric data; // for (size_t i = 0; i != iterations; ++i) { for (size_t i = iterations; i != 0; --i) { data.insert(std::to_string(i), i); @@ -152,7 +152,7 @@ TEMPLATE_TEST_CASE("Data - inserting fundamental types", "[.Data-benchmark]", st TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) { constexpr size_t iterations = 20000; - DataGeneric data; + QCInputsGeneric data; for (size_t i = 0; i != iterations; ++i) { data.insert(std::to_string(i), i); } @@ -174,7 +174,7 @@ TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[.Data-benchmark]", st TEMPLATE_TEST_CASE("Data - get fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) { constexpr size_t iterations = 20000; - DataGeneric data; + QCInputsGeneric data; for (size_t i = 0; i != iterations; ++i) { data.insert(std::to_string(i), i); } @@ -221,7 +221,7 @@ TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", st BENCHMARK("insert - iterate MOs") { - DataGeneric data; + QCInputsGeneric data; for (const auto& mo : MOs) { data.insert(mo->getFullName(), mo); } @@ -240,7 +240,7 @@ TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", st TEST_CASE("Data adapters - helper functions", "[Data]") { - Data data; + QCInputs data; { for (size_t i{}; i != 10; ++i) { const auto iStr = std::to_string(i); diff --git a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h index c34869eed2..6f5ac320d1 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h @@ -33,7 +33,7 @@ class SkeletonAggregator : public o2::quality_control::checker::AggregatorInterf public: // Override interface void configure() override; - std::map aggregate(const o2::quality_control::core::Data& data) override; + std::map aggregate(const o2::quality_control::core::QCInputs& data) override; ClassDefOverride(SkeletonAggregator, 1); }; diff --git a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h index a2cad2e46c..187a43038d 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h @@ -35,7 +35,7 @@ class SkeletonCheck : public o2::quality_control::checker::CheckInterface // Override interface void configure() override; Quality check(std::map>* moMap) override; - Quality check(const quality_control::core::Data& data) override; + Quality check(const quality_control::core::QCInputs& data) override; void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; void reset() override; void startOfActivity(const Activity& activity) override; diff --git a/Modules/Skeleton/src/SkeletonAggregator.cxx b/Modules/Skeleton/src/SkeletonAggregator.cxx index 965e242494..7857832dc7 100644 --- a/Modules/Skeleton/src/SkeletonAggregator.cxx +++ b/Modules/Skeleton/src/SkeletonAggregator.cxx @@ -33,7 +33,7 @@ void SkeletonAggregator::configure() std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey", "fallback value"); } -std::map SkeletonAggregator::aggregate(const o2::quality_control::core::Data& data) +std::map SkeletonAggregator::aggregate(const o2::quality_control::core::QCInputs& data) { // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. std::map result; diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index 8a7d873971..ad9517e75e 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -48,7 +48,7 @@ Quality SkeletonCheck::check(std::map Date: Wed, 27 Aug 2025 16:11:45 +0200 Subject: [PATCH 10/12] docs --- Framework/CMakeLists.txt | 5 +- .../QualityControl/AggregatorInterface.h | 2 +- .../include/QualityControl/CheckInterface.h | 2 +- Framework/include/QualityControl/Data.h | 87 -------- .../include/QualityControl/DataAdapters.h | 49 ----- Framework/include/QualityControl/QCInputs.h | 189 ++++++++++++++++++ .../QualityControl/{Data.inl => QCInputs.inl} | 50 +++-- .../include/QualityControl/QCInputsAdapters.h | 94 +++++++++ ...{DataAdapters.inl => QCInputsAdapters.inl} | 12 +- .../include/QualityControl/QCInputsFactory.h | 41 ++++ Framework/src/AggregatorInterface.cxx | 2 +- Framework/src/CheckInterface.cxx | 4 +- ...{DataAdapters.cxx => QCInputsAdapters.cxx} | 21 +- Framework/src/QCInputsFactory.cxx | 40 ++++ .../test/{testData.cxx => testQCInputs.cxx} | 8 +- .../include/Skeleton/SkeletonAggregator.h | 2 +- .../Skeleton/include/Skeleton/SkeletonCheck.h | 1 - Modules/Skeleton/src/SkeletonAggregator.cxx | 2 +- Modules/Skeleton/src/SkeletonCheck.cxx | 13 +- 19 files changed, 422 insertions(+), 202 deletions(-) delete mode 100644 Framework/include/QualityControl/Data.h delete mode 100644 Framework/include/QualityControl/DataAdapters.h create mode 100644 Framework/include/QualityControl/QCInputs.h rename Framework/include/QualityControl/{Data.inl => QCInputs.inl} (78%) create mode 100644 Framework/include/QualityControl/QCInputsAdapters.h rename Framework/include/QualityControl/{DataAdapters.inl => QCInputsAdapters.inl} (93%) create mode 100644 Framework/include/QualityControl/QCInputsFactory.h rename Framework/src/{DataAdapters.cxx => QCInputsAdapters.cxx} (71%) create mode 100644 Framework/src/QCInputsFactory.cxx rename Framework/test/{testData.cxx => testQCInputs.cxx} (98%) diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index 8ec95bf331..ed585776f6 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -137,7 +137,8 @@ add_library(O2QualityControl src/KafkaPoller.cxx src/FlagHelpers.cxx src/ObjectMetadataHelpers.cxx - src/DataAdapters.cxx + src/QCInputsAdapters.cxx + src/QCInputsFactory.cxx ) target_include_directories( @@ -291,7 +292,7 @@ add_executable(o2-qc-test-core test/testKafkaTests.cxx test/testFlagHelpers.cxx test/testQualitiesToFlagCollectionConverter.cxx - test/testData.cxx + test/testQCInputs.cxx ) set_property(TARGET o2-qc-test-core PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) diff --git a/Framework/include/QualityControl/AggregatorInterface.h b/Framework/include/QualityControl/AggregatorInterface.h index fd5c13e13a..c91fc1da1b 100644 --- a/Framework/include/QualityControl/AggregatorInterface.h +++ b/Framework/include/QualityControl/AggregatorInterface.h @@ -23,7 +23,7 @@ #include "QualityControl/UserCodeInterface.h" #include "QualityControl/Quality.h" #include "QualityControl/Activity.h" -#include "QualityControl/Data.h" +#include "QualityControl/QCInputs.h" namespace o2::quality_control::checker { diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index d95334d278..6b0d487926 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -22,7 +22,7 @@ #include "QualityControl/UserCodeInterface.h" #include "QualityControl/Activity.h" -#include "QualityControl/Data.h" +#include "QualityControl/QCInputs.h" namespace o2::quality_control::core { diff --git a/Framework/include/QualityControl/Data.h b/Framework/include/QualityControl/Data.h deleted file mode 100644 index b59a60fd0b..0000000000 --- a/Framework/include/QualityControl/Data.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 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 Data.h -/// \author Michal Tichak -/// - -#ifndef QC_CORE_DATA_H -#define QC_CORE_DATA_H - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace o2::quality_control::core -{ - -template -concept invocable_r = std::invocable && std::same_as, Result>; - -template -class QCInputsGeneric -{ - public: - QCInputsGeneric() = default; - - template - std::optional> get(std::string_view key); - - template - void emplace(std::string_view key, Args&&... args); - - template - void insert(std::string_view key, const T& value); - - template - auto iterateByType() const; - - template &> Pred> - auto iterateByTypeAndFilter(Pred&& filter) const; - - template &> Pred, invocable_r Transform> - auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const; - - size_t size() const noexcept; - - private: - ContainerMap mObjects; -}; - -struct StringHash { - using is_transparent = void; // Required for heterogeneous lookup - - std::size_t operator()(const std::string& str) const - { - return std::hash{}(str); - } - - std::size_t operator()(std::string_view sv) const - { - return std::hash{}(sv); - } -}; - -using transparent_unordered_map = std::unordered_map>; - -using QCInputs = QCInputsGeneric; - -} // namespace o2::quality_control::core - -#include "Data.inl" - -#endif diff --git a/Framework/include/QualityControl/DataAdapters.h b/Framework/include/QualityControl/DataAdapters.h deleted file mode 100644 index afb88150a0..0000000000 --- a/Framework/include/QualityControl/DataAdapters.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 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 DataAdapters.h -/// \author Michal Tichak -/// - -#ifndef QC_CORE_DATA_ADAPTERS_H -#define QC_CORE_DATA_ADAPTERS_H - -#include "Data.h" -#include "QualityControl/MonitorObject.h" -#include "QualityObject.h" - -namespace o2::quality_control::core -{ - -QCInputs createData(const std::map>& moMap); -QCInputs createData(const QualityObjectsMapType& moMap); - -inline auto iterateMonitorObjects(const QCInputs& data); -inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); - -template -std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName); - -// returns first occurence of MO with given name (possible name clash) -template -std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName); - -inline auto iterateQualityObjects(const QCInputs& data); - -std::optional> getQualityObject(const QCInputs& data, std::string_view checkName); - -} // namespace o2::quality_control::core - -// Templates definitions -#include "QualityControl/DataAdapters.inl" - -#endif diff --git a/Framework/include/QualityControl/QCInputs.h b/Framework/include/QualityControl/QCInputs.h new file mode 100644 index 0000000000..f2e4fedd47 --- /dev/null +++ b/Framework/include/QualityControl/QCInputs.h @@ -0,0 +1,189 @@ +// Copyright 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 QCInputs.h +/// \author Michal Tichak +/// \brief Generic container for heterogeneous QC input data. +/// +/// \par Example +/// \code{.cpp} +/// QCInputs data; +/// data.insert("count", 42u); +/// if (auto opt = data.get("count")) { +/// std::cout << "Count: " << opt->get() << std::endl; +/// } +/// for (const auto& v : data.iterateByType()) { +/// // process each value +/// } +/// \endcode +/// + +#ifndef QC_CORE_DATA_H +#define QC_CORE_DATA_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief Requires a callable to return exactly the specified type. +/// \tparam Function Callable type to invoke. +/// \tparam Result Expected return type. +/// \tparam Args Argument types for invocation. +template +concept invocable_r = std::invocable && + std::same_as, Result>; + +/// \brief Heterogeneous storage for named QC input objects. +/// +/// Stores values in an std::any-based container keyed by strings, +/// offering type-safe get, iteration, filtering, and transformation. +/// Example of such container is transparent_unordered_map at the end of this file +template +class QCInputsGeneric +{ + public: + QCInputsGeneric() = default; + + /// \brief Retrieve the object stored under the given key with matching type. + /// \tparam Result Expected stored type. + /// \param key Identifier for the stored object. + /// \returns Optional reference to const Result if found desired item of type Result. + /// \par Example + /// \code{.cpp} + /// if (auto opt = data.get("count")) { + /// if (opt.has_value()){ + /// const unsigned& value = opt.value(); // careful about using auto here as we want to invoke implicit conversion operator of reference_wrapper + /// } + /// } + /// \endcode + template + std::optional> get(std::string_view key); + + /// \brief Construct and store an object of type T under the given key. + /// \tparam T Type to construct and store. + /// \param key Identifier under which to store the object. + /// \param args Arguments forwarded to T's constructor. + /// \par Example + /// \code{.cpp} + /// data.emplace("greeting", "hello"); + /// \endcode + template + void emplace(std::string_view key, Args&&... args); + + /// \brief Store a copy of value under the given key. + /// \tparam T Type of the value to store. + /// \param key Identifier under which to store the value. + /// \param value Const reference to the value to insert. + /// \par Example + /// \code{.cpp} + /// data.insert("count", 10u); + /// \endcode + template + void insert(std::string_view key, const T& value); + + /// \brief Iterate over all stored objects matching type Result. + /// \tparam Result Type filter for iteration. + /// \returns Range of const references to stored Result instances. + /// \par Example + /// \code{.cpp} + /// for (auto& val : data.iterateByResultype()) { + /// // use val + /// } + /// \endcode + template + auto iterateByType() const; + + /// \brief Iterate over stored objects of type Result satisfying a predicate. + /// \tparam Result type filter for iteration. + /// \tparam Pred Callable predicate on (key, Result*) pairs. + /// \param filter Predicate to apply for filtering entries. + /// \returns Range of const references to Result passing the filter. + /// \par Example + /// \code{.cpp} + /// auto even = [](auto const& pair) { return pair.second % 2 == 0; }; + /// for (auto& val : data.iterateByTypeAndFilter(even)) { + /// // use val + /// } + /// \endcode + template &> Pred> + auto iterateByTypeAndFilter(Pred&& filter) const; + + /// \brief Filter entries of type Stored, then transform to type Result. + /// \tparam Stored Original stored type for filtering. + /// \tparam Result Target type after transformation. + /// \tparam Pred Callable predicate on (key, Stored*) pairs. + /// \tparam Transform Callable transforming Stored* to Result*. + /// This Callable can return nullptr but it will be filtered out + /// from results + /// \param filter Predicate to apply before transformation. + /// \param transform Callable to convert Stored to Result. + /// \returns Range of const references to resulting objects. + /// \par Example + /// \code{.cpp} + /// // if we stored some MOs that are not TH1F, these will be filtered out of results + /// auto toHistogram = [](auto const& p) { return dynamic_cast(p.second->getObject()); }; + /// for (auto& h : data.iterateByTypeFilterAndTransform([](auto const& p){ return p.first=="histo"; }, toHistogram)) { + /// // use histogram h + /// } + /// \endcode + template &> Pred, invocable_r Transform> + auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const; + + /// \brief Number of stored entries. + /// \returns Size of the underlying container. + /// \par Example + /// \code{.cpp} + /// size_t n = data.size(); + /// \endcode + size_t size() const noexcept; + + private: + ContainerMap mObjects; +}; + +/// \brief Transparent hash functor for string and string_view. +/// +/// Enables heterogeneous lookup in unordered maps keyed by std::string. +struct StringHash { + using is_transparent = void; // Required for heterogeneous lookup + + std::size_t operator()(const std::string& str) const + { + return std::hash{}(str); + } + + std::size_t operator()(std::string_view sv) const + { + return std::hash{}(sv); + } +}; + +/// \brief Unordered map storing std::any values with heterogeneous key lookup. It was chosen based on benchmark +/// in testQCInputs.cxx +using transparent_unordered_map = std::unordered_map>; + +/// \brief Default alias for QC inputs using transparent_unordered_map container. +using QCInputs = QCInputsGeneric; + +} // namespace o2::quality_control::core + +#include "QCInputs.inl" + +#endif diff --git a/Framework/include/QualityControl/Data.inl b/Framework/include/QualityControl/QCInputs.inl similarity index 78% rename from Framework/include/QualityControl/Data.inl rename to Framework/include/QualityControl/QCInputs.inl index 157ea8dc23..24dcfba19c 100644 --- a/Framework/include/QualityControl/Data.inl +++ b/Framework/include/QualityControl/QCInputs.inl @@ -10,7 +10,7 @@ // or submit itself to any jurisdiction. /// -/// \file Data.inl +/// \file QCInputs.inl /// \author Michal Tichak /// @@ -33,10 +33,10 @@ std::optional> QCInputsGeneric -template +template void QCInputsGeneric::emplace(std::string_view key, Args&&... args) { - mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); + mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); } template @@ -70,17 +70,35 @@ static const T* any_cast_try_shared_raw_ptr(const std::any& value) } template -static constexpr auto any_to_specific = std::views::transform([](const auto& pair) -> std::pair { return { pair.first, any_cast_try_shared_raw_ptr(pair.second) }; }); - -static constexpr auto filter_nullptr_in_pair = std::views::filter([](const auto& pair) -> bool { return pair.second != nullptr; }); - -static constexpr auto filter_nullptr = std::views::filter([](const auto* ptr) -> bool { return ptr != nullptr; }); - -static constexpr auto pair_to_reference = std::views::transform([](const auto& pair) -> const auto& { return *pair.second; }); - -static constexpr auto pair_to_value = std::views::transform([](const auto& pair) -> const auto* { return pair.second; }); - -static constexpr auto pointer_to_reference = std::views::transform([](const auto* ptr) -> const auto& { return *ptr; }); +static constexpr auto any_to_specific = std::views::transform( + [](const auto& pair) -> std::pair { + return { pair.first, any_cast_try_shared_raw_ptr(pair.second) }; + }); + +static constexpr auto filter_nullptr_in_pair = std::views::filter( + [](const auto& pair) -> bool { + return pair.second != nullptr; + }); + +static constexpr auto filter_nullptr = std::views::filter( + [](const auto* ptr) -> bool { + return ptr != nullptr; + }); + +static constexpr auto pair_to_value_const_ref = std::views::transform( + [](const auto& pair) -> const auto& { + return *pair.second; + }); + +static constexpr auto pair_to_value = std::views::transform( + [](const auto& pair) -> const auto* { + return pair.second; + }); + +static constexpr auto pointer_to_reference = std::views::transform( + [](const auto* ptr) -> const auto& { + return *ptr; + }); } // namespace internal @@ -89,7 +107,7 @@ template auto QCInputsGeneric::iterateByType() const { using namespace internal; - return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_reference; + return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_value_const_ref; } template @@ -97,7 +115,7 @@ template auto QCInputsGeneric::iterateByTypeAndFilter(Pred&& filter) const { using namespace internal; - return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_reference; + return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_value_const_ref; } template diff --git a/Framework/include/QualityControl/QCInputsAdapters.h b/Framework/include/QualityControl/QCInputsAdapters.h new file mode 100644 index 0000000000..0885765c8b --- /dev/null +++ b/Framework/include/QualityControl/QCInputsAdapters.h @@ -0,0 +1,94 @@ +// Copyright 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 QCInputsAdapters.h +/// \author Michal Tichak +/// \brief Adapters to build and query QCInputs from Monitor and Quality objects. +/// +/// \par Example +/// \code{.cpp} +/// // Iterate monitor objects by task name +/// for (const auto& mo : iterateMonitorObjects(data, "task1")) { +/// // use mo +/// } +/// // Retrieve a specific MonitorObject +/// if (auto opt = getMonitorObject(data, "objName", "task1")) { +/// const auto& mo = opt->get(); +/// // use mo +/// } +/// // Iterate and retrieve quality objects +/// for (const auto& qo : iterateQualityObjects(data)) { +/// // use qo +/// } +/// if (auto qoOpt = getQualityObject(data, "check1")) { +/// if(qoOpt.has_value()){ +/// QualityObject& qo = qoOpt->value(); +/// // ... +/// } +/// // use qoOpt->get() +/// } +/// \endcode +/// + +#ifndef QC_CORE_DATA_ADAPTERS_H +#define QC_CORE_DATA_ADAPTERS_H + +// Core inputs and adapters +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityObject.h" +#include "QCInputsFactory.h" + +namespace o2::quality_control::core +{ + +/// \brief Iterate over all MonitorObject entries in QCInputs. +inline auto iterateMonitorObjects(const QCInputs& data); + +/// \brief Iterate over MonitorObject entries filtered by task name. +/// \param data QCInputs containing MonitorObjects. +/// \param taskName Task name to filter entries. +inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); + +/// \brief Retrieve the first MonitorObject of type StoredType matching both name and task. +/// \tparam StoredType Type of MonitorObject or subclass to retrieve. +/// \param data QCInputs to search. +/// \param objectName Name of the MonitorObject. +/// \param taskName Name of the originating task. +/// \returns Optional reference to const StoredType if found. +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName); + +// returns first occurence of MO with given name (possible name clash) +/// \brief Retrieve the first MonitorObject of type StoredType matching name. +/// \tparam StoredType Type of MonitorObject or subclass to retrieve. +/// \param data QCInputs to search. +/// \param objectName Name of the MonitorObject. +/// \returns Optional reference to const StoredType if found. +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName); + +/// \brief Iterate over all QualityObject entries in QCInputs. +inline auto iterateQualityObjects(const QCInputs& data); + +/// \brief Retrieve the first QualityObject matching a given check name. +/// \param data QCInputs to search. +/// \param checkName Name of the quality check. +/// \returns Optional reference to const QualityObject if found. +std::optional> getQualityObject(const QCInputs& data, std::string_view checkName); + +} // namespace o2::quality_control::core + +// Templates definitions +#include "QCInputsAdapters.inl" + +#endif diff --git a/Framework/include/QualityControl/DataAdapters.inl b/Framework/include/QualityControl/QCInputsAdapters.inl similarity index 93% rename from Framework/include/QualityControl/DataAdapters.inl rename to Framework/include/QualityControl/QCInputsAdapters.inl index 09bc0acf54..0a44421914 100644 --- a/Framework/include/QualityControl/DataAdapters.inl +++ b/Framework/include/QualityControl/QCInputsAdapters.inl @@ -10,23 +10,19 @@ // or submit itself to any jurisdiction. /// -/// \file DataAdapters.inl +/// \file QCInputsAdapters.inl /// \author Michal Tichak /// -#ifndef QC_CORE_DATA_ADAPTERS_IMPL_H -#define QC_CORE_DATA_ADAPTERS_IMPL_H +#ifndef QC_CORE_DATA_ADAPTERS_INL +#define QC_CORE_DATA_ADAPTERS_INL #include #include -#include "QualityControl/Data.h" +#include "QCInputs.h" #include "QualityControl/MonitorObject.h" #include "QualityControl/QualityObject.h" -#ifdef QC_CORE_DATA_ADAPTERS_H -#include "QualityControl/DataAdapters.h" -#endif - namespace o2::quality_control::core { diff --git a/Framework/include/QualityControl/QCInputsFactory.h b/Framework/include/QualityControl/QCInputsFactory.h new file mode 100644 index 0000000000..a39ee15393 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsFactory.h @@ -0,0 +1,41 @@ +// Copyright 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 QCInputsFactory.h +/// \author Michal Tichak +/// \brief Factory functions to populate QCInputs from object maps. +/// + +#ifndef QC_CORE_QCINPUTSFACTORY_H +#define QC_CORE_QCINPUTSFACTORY_H + +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityObject.h" +#include +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief Create QCInputs from a map of MonitorObject instances. +/// \param moMap Map from name to shared MonitorObject pointer. +QCInputs createData(const std::map>& moMap); + +/// \brief Create QCInputs from a map of QualityObject instances. +/// \param qoMap Map from name to shared QualityObject pointer. +QCInputs createData(const QualityObjectsMapType& qoMap); + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/src/AggregatorInterface.cxx b/Framework/src/AggregatorInterface.cxx index 6f800bd848..26140fb4ef 100644 --- a/Framework/src/AggregatorInterface.cxx +++ b/Framework/src/AggregatorInterface.cxx @@ -15,7 +15,7 @@ /// #include "QualityControl/AggregatorInterface.h" -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputsAdapters.h" using namespace std; using namespace o2::quality_control::core; diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 123ca3d16d..3163946ce3 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -17,8 +17,8 @@ #include "QualityControl/CheckInterface.h" #include "QualityControl/ReferenceUtils.h" #include "QualityControl/MonitorObject.h" -#include "QualityControl/Data.h" -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" using namespace std; using namespace o2::quality_control::core; diff --git a/Framework/src/DataAdapters.cxx b/Framework/src/QCInputsAdapters.cxx similarity index 71% rename from Framework/src/DataAdapters.cxx rename to Framework/src/QCInputsAdapters.cxx index 0d331101dc..8d8f869c03 100644 --- a/Framework/src/DataAdapters.cxx +++ b/Framework/src/QCInputsAdapters.cxx @@ -10,32 +10,15 @@ // or submit itself to any jurisdiction. /// -/// \file DataAdapters.cxx +/// \file QCInputsAdapters.cxx /// \author Michal Tichak /// -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputsAdapters.h" namespace o2::quality_control::core { -QCInputs createData(const std::map>& moMap) -{ - QCInputs data; - for (const auto& [key, mo] : moMap) { - data.insert(key, mo); - } - return data; -} - -QCInputs createData(const QualityObjectsMapType& qoMap) -{ - QCInputs data; - for (const auto& [key, qo] : qoMap) { - data.insert(key, qo); - } - return data; -} std::optional> getQualityObject(const QCInputs& data, std::string_view objectName) { diff --git a/Framework/src/QCInputsFactory.cxx b/Framework/src/QCInputsFactory.cxx new file mode 100644 index 0000000000..545cedf004 --- /dev/null +++ b/Framework/src/QCInputsFactory.cxx @@ -0,0 +1,40 @@ +// Copyright 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 QCInputsFactory.cxx +/// \author Michal Tichak +/// + +#include "QualityControl/QCInputsFactory.h" + +namespace o2::quality_control::core +{ + +QCInputs createData(const std::map>& moMap) +{ + QCInputs data; + for (const auto& [key, mo] : moMap) { + data.insert(key, mo); + } + return data; +} + +QCInputs createData(const QualityObjectsMapType& qoMap) +{ + QCInputs data; + for (const auto& [key, qo] : qoMap) { + data.insert(key, qo); + } + return data; +} + +} // namespace o2::quality_control::core diff --git a/Framework/test/testData.cxx b/Framework/test/testQCInputs.cxx similarity index 98% rename from Framework/test/testData.cxx rename to Framework/test/testQCInputs.cxx index 73999b5897..3a6d7ca78f 100644 --- a/Framework/test/testData.cxx +++ b/Framework/test/testQCInputs.cxx @@ -10,17 +10,16 @@ // or submit itself to any jurisdiction. /// -/// \file testData.cxx +/// \file testQCInputs.cxx /// \author Michal Tichak /// #include #include #include -#include "Framework/include/QualityControl/DataAdapters.inl" #include "Framework/include/QualityControl/MonitorObject.h" -#include "QualityControl/Data.h" -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" #include #include #include @@ -187,6 +186,7 @@ TEMPLATE_TEST_CASE("Data - get fundamental types", "[.Data-benchmark]", stdmap, for (size_t i = 0; i != iterations; ++i) { auto opt = data.template get(std::to_string(i)); r += opt.value(); + auto& v = opt.value(); count++; } REQUIRE(count == iterations); diff --git a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h index 6f5ac320d1..7575e55b73 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h @@ -21,7 +21,7 @@ #include // QC #include "QualityControl/AggregatorInterface.h" -#include "QualityControl/Data.h" +#include "QualityControl/QCInputs.h" namespace o2::quality_control_modules::skeleton { diff --git a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h index 187a43038d..a15ba7713f 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h @@ -34,7 +34,6 @@ class SkeletonCheck : public o2::quality_control::checker::CheckInterface // Override interface void configure() override; - Quality check(std::map>* moMap) override; Quality check(const quality_control::core::QCInputs& data) override; void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; void reset() override; diff --git a/Modules/Skeleton/src/SkeletonAggregator.cxx b/Modules/Skeleton/src/SkeletonAggregator.cxx index 7857832dc7..396936eed0 100644 --- a/Modules/Skeleton/src/SkeletonAggregator.cxx +++ b/Modules/Skeleton/src/SkeletonAggregator.cxx @@ -16,7 +16,7 @@ #include "Skeleton/SkeletonAggregator.h" #include "QualityControl/QcInfoLogger.h" -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputsAdapters.h" using namespace std; using namespace o2::quality_control::core; diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index ad9517e75e..cc49ec628a 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -19,8 +19,8 @@ #include "QualityControl/Quality.h" #include "QualityControl/QcInfoLogger.h" #include "Skeleton/SkeletonTask.h" -#include "QualityControl/Data.h" -#include "QualityControl/DataAdapters.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" // ROOT #include @@ -42,12 +42,6 @@ void SkeletonCheck::configure() std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey1", "default"); } -Quality SkeletonCheck::check(std::map>* moMap) -{ - auto data = createData(*moMap); - return check(data); -} - Quality SkeletonCheck::check(const quality_control::core::QCInputs& data) { // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. @@ -66,7 +60,8 @@ Quality SkeletonCheck::check(const quality_control::core::QCInputs& data) return result; } - const TH1& histogram = histOpt.value().get(); + // histOpt contains reference_wrapper, it can be accesed by .get() or by implicit type conversion operator like in this example + const TH1& histogram = histOpt.value(); // unless we find issues, we assume the quality is good result = Quality::Good; From 97af63e693af3e7322ea9db81a259d871955835d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Wed, 27 Aug 2025 16:16:25 +0200 Subject: [PATCH 11/12] iterateQualityObjects with check name --- .../include/QualityControl/QCInputsAdapters.h | 5 +++++ .../include/QualityControl/QCInputsAdapters.inl | 8 ++++++++ Framework/src/CheckInterface.cxx | 14 -------------- Framework/src/QCInputsAdapters.cxx | 1 - 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Framework/include/QualityControl/QCInputsAdapters.h b/Framework/include/QualityControl/QCInputsAdapters.h index 0885765c8b..7b75b94c03 100644 --- a/Framework/include/QualityControl/QCInputsAdapters.h +++ b/Framework/include/QualityControl/QCInputsAdapters.h @@ -80,6 +80,11 @@ std::optional> getMonitorObject(const Q /// \brief Iterate over all QualityObject entries in QCInputs. inline auto iterateQualityObjects(const QCInputs& data); +/// \brief Iterate over QualityObject entries filtered by check name. +/// \param data QCInputs containing QualityObjects. +/// \param checkName Check name to filter entries. +inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName); + /// \brief Retrieve the first QualityObject matching a given check name. /// \param data QCInputs to search. /// \param checkName Name of the quality check. diff --git a/Framework/include/QualityControl/QCInputsAdapters.inl b/Framework/include/QualityControl/QCInputsAdapters.inl index 0a44421914..5eaf387826 100644 --- a/Framework/include/QualityControl/QCInputsAdapters.inl +++ b/Framework/include/QualityControl/QCInputsAdapters.inl @@ -88,6 +88,14 @@ inline auto iterateQualityObjects(const QCInputs& data) return data.iterateByType(); } +inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName) +{ + const auto filterQOByName = [checkName](const auto& pair) { + return std::string_view(pair.second->getName()) == checkName; + }; + return data.iterateByTypeAndFilter(filterQOByName); +} + } // namespace o2::quality_control::core #endif diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 3163946ce3..3a35edf8cb 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -37,20 +37,6 @@ core::Quality CheckInterface::check(const core::QCInputs& data) return core::Quality{}; }; -std::string CheckInterface::getAcceptedType() { return "TObject"; } - -bool CheckInterface::isObjectCheckable(const std::shared_ptr mo) -{ - return isObjectCheckable(mo.get()); -} - -bool CheckInterface::isObjectCheckable(const MonitorObject* mo) -{ - TObject* encapsulated = mo->getObject(); - - return encapsulated->IsA()->InheritsFrom(getAcceptedType().c_str()); -} - void CheckInterface::configure() { // noop, override it if you want. diff --git a/Framework/src/QCInputsAdapters.cxx b/Framework/src/QCInputsAdapters.cxx index 8d8f869c03..bc1d83873e 100644 --- a/Framework/src/QCInputsAdapters.cxx +++ b/Framework/src/QCInputsAdapters.cxx @@ -19,7 +19,6 @@ namespace o2::quality_control::core { - std::optional> getQualityObject(const QCInputs& data, std::string_view objectName) { const auto filterQOByName = [objectName](const auto& pair) { From 779ca8e2b1d5af2cac3c0744388cb9a0278ff4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Mon, 8 Sep 2025 17:58:37 +0200 Subject: [PATCH 12/12] reworded docs and removed generic container from QCInputs --- .../include/QualityControl/CheckInterface.h | 2 + Framework/include/QualityControl/QCInputs.h | 82 +++++++------- Framework/include/QualityControl/QCInputs.inl | 21 ++-- .../include/QualityControl/QCInputsAdapters.h | 4 +- .../QualityControl/QCInputsAdapters.inl | 2 +- Framework/src/QCInputsAdapters.cxx | 2 +- Framework/test/testQCInputs.cxx | 106 ------------------ 7 files changed, 52 insertions(+), 167 deletions(-) diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index 6b0d487926..16d8ac05ab 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -48,6 +48,8 @@ class CheckInterface : public core::UserCodeInterface virtual ~CheckInterface() = default; /// \brief Returns the quality associated with these objects. + /// \deprecated This function won't be deleted in future releases for compatibility reasons but users should + /// use check(const Data&) for any new Checks. /// /// @param moMap A map of the the MonitorObjects to check and their full names (i.e. /) as keys. /// @return The quality associated with these objects. diff --git a/Framework/include/QualityControl/QCInputs.h b/Framework/include/QualityControl/QCInputs.h index f2e4fedd47..50f95ddf9c 100644 --- a/Framework/include/QualityControl/QCInputs.h +++ b/Framework/include/QualityControl/QCInputs.h @@ -17,11 +17,13 @@ /// \par Example /// \code{.cpp} /// QCInputs data; -/// data.insert("count", 42u); -/// if (auto opt = data.get("count")) { -/// std::cout << "Count: " << opt->get() << std::endl; +/// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); +/// data.insert("mo", std::make_shared(h1, "taskname", "class1", "TST")); +/// if (auto opt = data.get("mo")) { +/// MonitorObject& moObject = opt.value(); +/// std::cout << "mo name: " << moObject.getName() << std::endl; /// } -/// for (const auto& v : data.iterateByType()) { +/// for (const auto& mo : data.iterateByType()) { /// // process each value /// } /// \endcode @@ -52,14 +54,12 @@ concept invocable_r = std::invocable && /// \brief Heterogeneous storage for named QC input objects. /// -/// Stores values in an std::any-based container keyed by strings, +/// Stores values in an std::unordered_map while /// offering type-safe get, iteration, filtering, and transformation. -/// Example of such container is transparent_unordered_map at the end of this file -template -class QCInputsGeneric +class QCInputs { public: - QCInputsGeneric() = default; + QCInputs() = default; /// \brief Retrieve the object stored under the given key with matching type. /// \tparam Result Expected stored type. @@ -67,7 +67,7 @@ class QCInputsGeneric /// \returns Optional reference to const Result if found desired item of type Result. /// \par Example /// \code{.cpp} - /// if (auto opt = data.get("count")) { + /// if (auto opt = data.get("mo")) { /// if (opt.has_value()){ /// const unsigned& value = opt.value(); // careful about using auto here as we want to invoke implicit conversion operator of reference_wrapper /// } @@ -82,7 +82,8 @@ class QCInputsGeneric /// \param args Arguments forwarded to T's constructor. /// \par Example /// \code{.cpp} - /// data.emplace("greeting", "hello"); + /// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + /// data.emplace("mo", h1, "taskname", "class1", "TST"); /// \endcode template void emplace(std::string_view key, Args&&... args); @@ -93,7 +94,8 @@ class QCInputsGeneric /// \param value Const reference to the value to insert. /// \par Example /// \code{.cpp} - /// data.insert("count", 10u); + /// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + /// data.insert("mo", std::make_shared(h1, "taskname", "class1", "TST")); /// \endcode template void insert(std::string_view key, const T& value); @@ -103,7 +105,7 @@ class QCInputsGeneric /// \returns Range of const references to stored Result instances. /// \par Example /// \code{.cpp} - /// for (auto& val : data.iterateByResultype()) { + /// for (auto& mo : data.iterateByType()) { /// // use val /// } /// \endcode @@ -117,9 +119,9 @@ class QCInputsGeneric /// \returns Range of const references to Result passing the filter. /// \par Example /// \code{.cpp} - /// auto even = [](auto const& pair) { return pair.second % 2 == 0; }; - /// for (auto& val : data.iterateByTypeAndFilter(even)) { - /// // use val + /// auto nameFilter = [](auto const& pair) { return pair.second->getName() == "name"; }; + /// for (auto& mo : data.iterateByTypeAndFilter(nameFilter)) { + /// // use mo /// } /// \endcode template &> Pred> @@ -138,8 +140,9 @@ class QCInputsGeneric /// \par Example /// \code{.cpp} /// // if we stored some MOs that are not TH1F, these will be filtered out of results - /// auto toHistogram = [](auto const& p) { return dynamic_cast(p.second->getObject()); }; - /// for (auto& h : data.iterateByTypeFilterAndTransform([](auto const& p){ return p.first=="histo"; }, toHistogram)) { + /// auto toHistogram = [](auto const& p) -> const auto* { return dynamic_cast(p.second->getObject()); }; + /// auto nameFilter = [](auto const& p){ return p.first == "histo"; }; + /// for (auto& h : data.iterateByTypeFilterAndTransform(nameFilter, toHistogram)) { /// // use histogram h /// } /// \endcode @@ -155,33 +158,26 @@ class QCInputsGeneric size_t size() const noexcept; private: - ContainerMap mObjects; -}; - -/// \brief Transparent hash functor for string and string_view. -/// -/// Enables heterogeneous lookup in unordered maps keyed by std::string. -struct StringHash { - using is_transparent = void; // Required for heterogeneous lookup - - std::size_t operator()(const std::string& str) const - { - return std::hash{}(str); - } - - std::size_t operator()(std::string_view sv) const - { - return std::hash{}(sv); - } + /// \brief Transparent hash functor for string and string_view. + /// + /// Enables heterogeneous lookup in unordered maps keyed by std::string. + struct StringHash { + using is_transparent = void; // Required for heterogeneous lookup + + std::size_t operator()(const std::string& str) const + { + return std::hash{}(str); + } + + std::size_t operator()(std::string_view sv) const + { + return std::hash{}(sv); + } + }; + + std::unordered_map> mObjects; }; -/// \brief Unordered map storing std::any values with heterogeneous key lookup. It was chosen based on benchmark -/// in testQCInputs.cxx -using transparent_unordered_map = std::unordered_map>; - -/// \brief Default alias for QC inputs using transparent_unordered_map container. -using QCInputs = QCInputsGeneric; - } // namespace o2::quality_control::core #include "QCInputs.inl" diff --git a/Framework/include/QualityControl/QCInputs.inl b/Framework/include/QualityControl/QCInputs.inl index 24dcfba19c..79795fca09 100644 --- a/Framework/include/QualityControl/QCInputs.inl +++ b/Framework/include/QualityControl/QCInputs.inl @@ -20,9 +20,8 @@ namespace o2::quality_control::core { -template template -std::optional> QCInputsGeneric::get(std::string_view key) +std::optional> QCInputs::get(std::string_view key) { if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { @@ -32,16 +31,14 @@ std::optional> QCInputsGeneric template -void QCInputsGeneric::emplace(std::string_view key, Args&&... args) +void QCInputs::emplace(std::string_view key, Args&&... args) { mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); } -template template -void QCInputsGeneric::insert(std::string_view key, const T& value) +void QCInputs::insert(std::string_view key, const T& value) { mObjects.insert({ std::string{ key }, value }); } @@ -102,25 +99,22 @@ static constexpr auto pointer_to_reference = std::views::transform( } // namespace internal -template template -auto QCInputsGeneric::iterateByType() const +auto QCInputs::iterateByType() const { using namespace internal; return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_value_const_ref; } -template template &> Pred> -auto QCInputsGeneric::iterateByTypeAndFilter(Pred&& filter) const +auto QCInputs::iterateByTypeAndFilter(Pred&& filter) const { using namespace internal; return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_value_const_ref; } -template template &> Pred, invocable_r Transform> -auto QCInputsGeneric::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const +auto QCInputs::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const { using namespace internal; return mObjects | @@ -133,8 +127,7 @@ auto QCInputsGeneric::iterateByTypeFilterAndTransform(Pred&& filte pointer_to_reference; } -template -size_t QCInputsGeneric::size() const noexcept +inline size_t QCInputs::size() const noexcept { return mObjects.size(); } diff --git a/Framework/include/QualityControl/QCInputsAdapters.h b/Framework/include/QualityControl/QCInputsAdapters.h index 7b75b94c03..444e218d09 100644 --- a/Framework/include/QualityControl/QCInputsAdapters.h +++ b/Framework/include/QualityControl/QCInputsAdapters.h @@ -60,7 +60,7 @@ inline auto iterateMonitorObjects(const QCInputs& data); inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); /// \brief Retrieve the first MonitorObject of type StoredType matching both name and task. -/// \tparam StoredType Type of MonitorObject or subclass to retrieve. +/// \tparam StoredType Type of MonitorObject or stored class to retrieve. /// \param data QCInputs to search. /// \param objectName Name of the MonitorObject. /// \param taskName Name of the originating task. @@ -70,7 +70,7 @@ std::optional> getMonitorObject(const Q // returns first occurence of MO with given name (possible name clash) /// \brief Retrieve the first MonitorObject of type StoredType matching name. -/// \tparam StoredType Type of MonitorObject or subclass to retrieve. +/// \tparam StoredType Type of MonitorObject or stored class to retrieve. /// \param data QCInputs to search. /// \param objectName Name of the MonitorObject. /// \returns Optional reference to const StoredType if found. diff --git a/Framework/include/QualityControl/QCInputsAdapters.inl b/Framework/include/QualityControl/QCInputsAdapters.inl index 5eaf387826..26d2fe2e92 100644 --- a/Framework/include/QualityControl/QCInputsAdapters.inl +++ b/Framework/include/QualityControl/QCInputsAdapters.inl @@ -91,7 +91,7 @@ inline auto iterateQualityObjects(const QCInputs& data) inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName) { const auto filterQOByName = [checkName](const auto& pair) { - return std::string_view(pair.second->getName()) == checkName; + return std::string_view(pair.second->getCheckName()) == checkName; }; return data.iterateByTypeAndFilter(filterQOByName); } diff --git a/Framework/src/QCInputsAdapters.cxx b/Framework/src/QCInputsAdapters.cxx index bc1d83873e..a8cc23e04e 100644 --- a/Framework/src/QCInputsAdapters.cxx +++ b/Framework/src/QCInputsAdapters.cxx @@ -22,7 +22,7 @@ namespace o2::quality_control::core std::optional> getQualityObject(const QCInputs& data, std::string_view objectName) { const auto filterQOByName = [objectName](const auto& pair) { - return std::string_view(pair.second->GetName()) == objectName; + return std::string_view(pair.second->getCheckName()) == objectName; }; for (const auto& qo : data.iterateByTypeAndFilter(filterQOByName)) { return { qo }; diff --git a/Framework/test/testQCInputs.cxx b/Framework/test/testQCInputs.cxx index 3a6d7ca78f..0c54494ea5 100644 --- a/Framework/test/testQCInputs.cxx +++ b/Framework/test/testQCInputs.cxx @@ -131,112 +131,6 @@ TEST_CASE("Data - raw pointers", "[Data]") REQUIRE(count == 2); } -using stdmap = std::map>; -using boostflatmap = boost::container::flat_map>; - -TEMPLATE_TEST_CASE("Data - inserting fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) -{ - constexpr size_t iterations = 20'000; - - BENCHMARK("insert size_t") - { - QCInputsGeneric data; - // for (size_t i = 0; i != iterations; ++i) { - for (size_t i = iterations; i != 0; --i) { - data.insert(std::to_string(i), i); - } - }; -} - -TEMPLATE_TEST_CASE("Data - iterating fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) -{ - constexpr size_t iterations = 20000; - QCInputsGeneric data; - for (size_t i = 0; i != iterations; ++i) { - data.insert(std::to_string(i), i); - } - - REQUIRE(data.size() == iterations); - BENCHMARK("iterate size_t") - { - REQUIRE(data.size() == iterations); - size_t r{}; - size_t count{}; - for (const auto& v : data.template iterateByType()) { - r += v; - count++; - } - REQUIRE(count == iterations); - }; -} - -TEMPLATE_TEST_CASE("Data - get fundamental types", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) -{ - constexpr size_t iterations = 20000; - QCInputsGeneric data; - for (size_t i = 0; i != iterations; ++i) { - data.insert(std::to_string(i), i); - } - - REQUIRE(data.size() == iterations); - BENCHMARK("iterate size_t") - { - size_t r{}; - size_t count{}; - for (size_t i = 0; i != iterations; ++i) { - auto opt = data.template get(std::to_string(i)); - r += opt.value(); - auto& v = opt.value(); - count++; - } - REQUIRE(count == iterations); - }; -} - -std::string generateRandomString(size_t length) -{ - static constexpr std::string_view CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - thread_local std::mt19937 generator(std::random_device{}()); - std::uniform_int_distribution distribution(0, CHARACTERS.length() - 1); - - std::string random_string; - random_string.reserve(length); - for (size_t i = 0; i < length; ++i) { - random_string += CHARACTERS[distribution(generator)]; - } - return random_string; -} - -TEMPLATE_TEST_CASE("Data - inserting and iterating MOs", "[.Data-benchmark]", stdmap, boostflatmap, transparent_unordered_map) -{ - constexpr size_t iterations = 1000; - std::vector> MOs; - - for (size_t i = 0; i != iterations; ++i) { - const auto name = generateRandomString(20); - auto* h = new TH1F(name.c_str(), name.c_str(), 100, 0, 99); - std::shared_ptr mo = std::make_shared(h, "taskname", "class1", "TST"); - MOs.push_back(mo); - } - - BENCHMARK("insert - iterate MOs") - { - QCInputsGeneric data; - for (const auto& mo : MOs) { - data.insert(mo->getFullName(), mo); - } - - const auto filterMOByName = [](const auto& pair) { - return std::string_view(pair.second->GetName()) == "nonexistent"; - }; - - const auto getInternalObject = [](const MonitorObject* ptr) -> const auto* { - return dynamic_cast(ptr->getObject()); - }; - REQUIRE(data.template iterateByTypeFilterAndTransform(filterMOByName, getInternalObject).empty()); - }; -} - TEST_CASE("Data adapters - helper functions", "[Data]") {