diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index 8150becbff..ed585776f6 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -136,7 +136,10 @@ add_library(O2QualityControl src/ReductorHelpers.cxx src/KafkaPoller.cxx src/FlagHelpers.cxx - src/ObjectMetadataHelpers.cxx) + src/ObjectMetadataHelpers.cxx + src/QCInputsAdapters.cxx + src/QCInputsFactory.cxx +) target_include_directories( O2QualityControl @@ -289,6 +292,7 @@ add_executable(o2-qc-test-core test/testKafkaTests.cxx test/testFlagHelpers.cxx test/testQualitiesToFlagCollectionConverter.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 f33ec20171..c91fc1da1b 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/QCInputs.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::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 7d78fd4bc3..16d8ac05ab 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -22,11 +22,14 @@ #include "QualityControl/UserCodeInterface.h" #include "QualityControl/Activity.h" +#include "QualityControl/QCInputs.h" + namespace o2::quality_control::core { class Activity; class MonitorObject; -} + +} // namespace o2::quality_control::core using namespace o2::quality_control::core; @@ -45,10 +48,18 @@ 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. - 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::QCInputs& data); /// \brief Modify the aspect of the plot. /// diff --git a/Framework/include/QualityControl/QCInputs.h b/Framework/include/QualityControl/QCInputs.h new file mode 100644 index 0000000000..50f95ddf9c --- /dev/null +++ b/Framework/include/QualityControl/QCInputs.h @@ -0,0 +1,185 @@ +// 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; +/// 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& mo : 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::unordered_map while +/// offering type-safe get, iteration, filtering, and transformation. +class QCInputs +{ + public: + QCInputs() = 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("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 + /// } + /// } + /// \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} + /// 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); + + /// \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} + /// 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); + + /// \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& mo : data.iterateByType()) { + /// // 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 nameFilter = [](auto const& pair) { return pair.second->getName() == "name"; }; + /// for (auto& mo : data.iterateByTypeAndFilter(nameFilter)) { + /// // use mo + /// } + /// \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) -> 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 + 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: + /// \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; +}; + +} // namespace o2::quality_control::core + +#include "QCInputs.inl" + +#endif diff --git a/Framework/include/QualityControl/QCInputs.inl b/Framework/include/QualityControl/QCInputs.inl new file mode 100644 index 0000000000..79795fca09 --- /dev/null +++ b/Framework/include/QualityControl/QCInputs.inl @@ -0,0 +1,135 @@ +// 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.inl +/// \author Michal Tichak +/// + +#include +#include + +namespace o2::quality_control::core +{ + +template +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) { + return { *casted }; + } + } + return std::nullopt; +} + +template +void QCInputs::emplace(std::string_view key, Args&&... args) +{ + mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); +} + +template +void QCInputs::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_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 + +template +auto QCInputs::iterateByType() const +{ + using namespace internal; + return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_value_const_ref; +} + +template &> Pred> +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 &> Pred, invocable_r Transform> +auto QCInputs::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; +} + +inline size_t QCInputs::size() const noexcept +{ + return mObjects.size(); +} + +} // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/QCInputsAdapters.h b/Framework/include/QualityControl/QCInputsAdapters.h new file mode 100644 index 0000000000..444e218d09 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsAdapters.h @@ -0,0 +1,99 @@ +// 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 stored class 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 stored class 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 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. +/// \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/QCInputsAdapters.inl b/Framework/include/QualityControl/QCInputsAdapters.inl new file mode 100644 index 0000000000..26d2fe2e92 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsAdapters.inl @@ -0,0 +1,101 @@ +// 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.inl +/// \author Michal Tichak +/// + +#ifndef QC_CORE_DATA_ADAPTERS_INL +#define QC_CORE_DATA_ADAPTERS_INL + +#include +#include +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" + +namespace o2::quality_control::core +{ + +inline auto iterateMonitorObjects(const o2::quality_control::core::QCInputs& data) +{ + return data.iterateByType(); +} + +inline auto iterateMonitorObjects(const QCInputs& 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 QCInputs& 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.iterateByTypeFilterAndTransform(filter, getInternalObject)) { + return { v }; + } + } + return std::nullopt; +} + +} // namespace helpers + +template +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 }; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByNameAndTaskName); +} + +template +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; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByName); +} + +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->getCheckName()) == checkName; + }; + return data.iterateByTypeAndFilter(filterQOByName); +} + +} // namespace o2::quality_control::core + +#endif 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 ed96e5052f..26140fb4ef 100644 --- a/Framework/src/AggregatorInterface.cxx +++ b/Framework/src/AggregatorInterface.cxx @@ -15,6 +15,7 @@ /// #include "QualityControl/AggregatorInterface.h" +#include "QualityControl/QCInputsAdapters.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::QCInputs& data) +{ + return {}; +} + void AggregatorInterface::startOfActivity(const Activity& activity) { // noop, override it if you want. diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 36d01d6a2f..3a35edf8cb 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -17,6 +17,8 @@ #include "QualityControl/CheckInterface.h" #include "QualityControl/ReferenceUtils.h" #include "QualityControl/MonitorObject.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" using namespace std; using namespace o2::quality_control::core; @@ -24,6 +26,17 @@ using namespace o2::quality_control::core; namespace o2::quality_control::checker { +core::Quality CheckInterface::check(std::map>* moMap) +{ + auto data = createData(*moMap); + return check(data); +}; + +core::Quality CheckInterface::check(const core::QCInputs& data) +{ + return core::Quality{}; +}; + void CheckInterface::configure() { // noop, override it if you want. diff --git a/Framework/src/QCInputsAdapters.cxx b/Framework/src/QCInputsAdapters.cxx new file mode 100644 index 0000000000..a8cc23e04e --- /dev/null +++ b/Framework/src/QCInputsAdapters.cxx @@ -0,0 +1,33 @@ +// 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.cxx +/// \author Michal Tichak +/// + +#include "QualityControl/QCInputsAdapters.h" + +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->getCheckName()) == objectName; + }; + for (const auto& qo : data.iterateByTypeAndFilter(filterQOByName)) { + return { qo }; + } + return std::nullopt; +} + +} // namespace o2::quality_control::core 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/testQCInputs.cxx b/Framework/test/testQCInputs.cxx new file mode 100644 index 0000000000..0c54494ea5 --- /dev/null +++ b/Framework/test/testQCInputs.cxx @@ -0,0 +1,205 @@ +// 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 testQCInputs.cxx +/// \author Michal Tichak +/// + +#include +#include +#include +#include "Framework/include/QualityControl/MonitorObject.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" +#include +#include +#include + +using namespace o2::quality_control::core; + +struct nonexistent { +}; + +TEST_CASE("Data - constructor", "[Data]") +{ + REQUIRE_NOTHROW([]() { QCInputs data{}; }); +} + +TEST_CASE("Data insert and get", "[Data]") +{ + QCInputs 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 == 1); +} + +TEST_CASE("Data - iterateByType", "[Data]") +{ + QCInputs data; + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); + REQUIRE(data.size() == 2); + + SECTION("iterate by int") + { + size_t count{}; + for (auto& v : data.iterateByType()) { + REQUIRE(v == 1); + count++; + } + REQUIRE(count == 1); + } + + SECTION("iterate by nonexistent") + { + size_t count{}; + REQUIRE(data.iterateByType().empty()); + } +} + +TEST_CASE("Data - iterateByTypeAndFilter", "[Data]") +{ + QCInputs data; + 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); +} + +TEST_CASE("Data - iterateByTypeFilterAndTransform", "[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"); + + QCInputs 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 - raw pointers", "[Data]") +{ + QCInputs 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); +} + +TEST_CASE("Data adapters - helper functions", "[Data]") +{ + + QCInputs 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/include/Skeleton/SkeletonAggregator.h b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h index f42d8af942..7575e55b73 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/QCInputs.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::QCInputs& data) override; ClassDefOverride(SkeletonAggregator, 1); }; } // namespace o2::quality_control_modules::skeleton -#endif //QUALITYCONTROL_SKELETONAGGREGATOR_H +#endif // QUALITYCONTROL_SKELETONAGGREGATOR_H diff --git a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h index 660984d72c..a15ba7713f 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h @@ -34,7 +34,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::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 985957a438..396936eed0 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/QCInputsAdapters.h" using namespace std; using namespace o2::quality_control::core; @@ -32,22 +33,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::QCInputs& 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 : 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 f5deb755ca..cc49ec628a 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -18,6 +18,9 @@ #include "QualityControl/MonitorObject.h" #include "QualityControl/Quality.h" #include "QualityControl/QcInfoLogger.h" +#include "Skeleton/SkeletonTask.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" // ROOT #include @@ -39,7 +42,7 @@ void SkeletonCheck::configure() std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey1", "default"); } -Quality SkeletonCheck::check(std::map>* moMap) +Quality SkeletonCheck::check(const quality_control::core::QCInputs& data) { // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. Quality result = Quality::Null; @@ -49,35 +52,36 @@ 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"); - } - } - // optionally, we can associate some custom metadata to a Quality - result.addMetadata("mykey", "myvalue"); + 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; + } + + // 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; + + // 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"); + return result; }