diff --git a/core/qa/CMakeLists.txt b/core/qa/CMakeLists.txt index f6e9c92237b240ae50f615be424d8c5146985fd4..0974dd5734efdb20c2996210f59c228d7297de8a 100644 --- a/core/qa/CMakeLists.txt +++ b/core/qa/CMakeLists.txt @@ -1,4 +1,5 @@ set(INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/checker ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -10,6 +11,13 @@ set(SRCS CbmQaTable.cxx CbmQaTask.cxx CbmQaIO.cxx + checker/CbmQaCheckerCore.cxx + checker/CbmQaCheckerFileHandler.cxx + checker/CbmQaCheckerHist1DHandler.cxx + checker/CbmQaCheckerHist2DHandler.cxx + checker/CbmQaCheckerObjectHandler.cxx + checker/CbmQaCheckerProfile1DHandler.cxx + checker/CbmQaCheckerObjectDB.cxx ) set(HEADERS @@ -27,12 +35,17 @@ set(PUBLIC_DEPENDENCIES ROOT::Hist ) +set(PRIVATE_DEPENDENCIES + external::yaml-cpp + ) + set(INTERFACE_DEPENDENCIES ROOT::Graf ) generate_cbm_library() -Install(FILES CbmQaTask.h CbmQaCanvas.h CbmQaTable.h CbmQaHist.h CbmQaEff.h CbmQaPie.h CbmQaConstants.h CbmQaCmpDrawer.h +Install(FILES CbmQaTask.h CbmQaCanvas.h CbmQaTable.h CbmQaHist.h CbmQaEff.h CbmQaPie.h CbmQaConstants.h CbmQaCmpDrawer.h checker/CbmQaCheckerTypedefs.h DESTINATION include - ) + ) + diff --git a/core/qa/CbmQaBaseLinkDef.h b/core/qa/CbmQaBaseLinkDef.h index 5d2349aa39f067724f5857aae91324c67eed5283..cee4c1d3419cd1c9eb13b4964891d7794c50fa68 100644 --- a/core/qa/CbmQaBaseLinkDef.h +++ b/core/qa/CbmQaBaseLinkDef.h @@ -25,5 +25,12 @@ #pragma link C++ class CbmQaHist < TProfile2D> + ; #pragma link C++ class CbmQaTable + ; #pragma link C++ class CbmQaTask + ; +#pragma link C++ class cbm::qa::checker::Core + ; +#pragma link C++ class cbm::qa::checker::FileHandler + ; +#pragma link C++ class cbm::qa::checker::Hist1DHandler + ; +#pragma link C++ class cbm::qa::checker::Hist2DHandler + ; +#pragma link C++ class cbm::qa::checker::Profile1DHandler + ; +#pragma link C++ class cbm::qa::checker::ObjectHandler + ; +#pragma link C++ class cbm::qa::checker::ObjectDB + ; #endif diff --git a/core/qa/checker/CbmQaCheckerCore.cxx b/core/qa/checker/CbmQaCheckerCore.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7164d12ad61c606f673e491448d2ed9c56cd53b4 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerCore.cxx @@ -0,0 +1,108 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerCore.cxx +/// @brief Core class of the QA checking framework (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 06.02.2023 + +#include "CbmQaCheckerCore.h" + +#include "CbmQaCheckerFileHandler.h" + +#include "Logger.h" + +#include "TClonesArray.h" +#include "TFolder.h" +//#include <boost/filesystem.hpp> +#include <regex> + +#include <yaml-cpp/yaml.h> + +using cbm::qa::checker::Core; + +// --------------------------------------------------------------------------------------------------------------------- +// +Core::Core() +{ + // Define object names data base + fpObjDB = std::make_shared<ObjectDB>(); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Core::AddVersion(const char* version, const char* pathName) { fpObjDB->AddVersion(version, pathName); } + +// --------------------------------------------------------------------------------------------------------------------- +// +void Core::AddDataset(const char* dataset) { fpObjDB->AddDataset(dataset); } + +// --------------------------------------------------------------------------------------------------------------------- +// +void Core::RegisterOutFile(const char* filename) +{ + LOG(info) << "Core: Registering output file: " << filename; + fpOutFile = std::make_unique<TFile>(filename, "RECREATE"); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Core::Process(Option_t* opt) +{ + // ----- Init the object database + fpObjDB->Init(); + + // ----- Register output file + if (fpOutFile == nullptr) { this->RegisterOutFile("QaCheckerOutput.root"); } + + // ----- Process datasets and files + int nDatasets = fpObjDB->GetNofDatasets(); + std::vector<TFolder*> vDSFolders(nDatasets, nullptr); + + for (int iDS = 0; iDS < nDatasets; ++iDS) { + // Create a new folder for dataset (/dataset) + auto* pDSDir = fpOutFile->mkdir(fpObjDB->GetDataset(iDS).data()); + + // Loop over files + for (int iFile = 0; iFile < fpObjDB->GetNofFiles(); ++iFile) { + // Define output folder for file (/dataset/file) + auto* pFileDir = pDSDir->mkdir(fpObjDB->GetFileLabel(iFile).data()); + + // Create and process a file handler + auto pFileHandler = std::make_unique<FileHandler>(fpObjDB, pFileDir, iDS, iFile); + pFileHandler->Process(opt); + } // iFile + } // iDS +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Core::SetFromYAML(const char* configName) { fpObjDB->ReadFromYAML(configName); } + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Core::Scan() const +{ + LOG(info) << "Core: Scanning comparison result ..."; + bool res = true; /// Equal + for (int iFile = 0; iFile < fpObjDB->GetNofFiles(); ++iFile) { + for (int iObj = 0; iObj < fpObjDB->GetNofObjects(iFile); ++iObj) { + for (int iDS = 0; iDS < fpObjDB->GetNofDatasets(); ++iDS) { + for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) { + // For now we check, if anything has been changed with respect to default. Later one can add a flag to test + // compatibility for different levels (for example, two histograms could be obtained from different systems, + // or different seeds, but still be consistent). + if (fpObjDB->GetCmpResult(iDS, iFile, iObj, iVer)) { + LOG(info) << "\t file: " << fpObjDB->GetInputFileName(iVer, iFile, iDS) + << ", object: " << fpObjDB->GetObject(iFile, iObj); + res = false; + } + } // iVer + } // iDS + } // iObj + } // iFile + LOG(info) << "Core: Scanning comparison result ... done"; + + return res; +} \ No newline at end of file diff --git a/core/qa/checker/CbmQaCheckerCore.h b/core/qa/checker/CbmQaCheckerCore.h new file mode 100644 index 0000000000000000000000000000000000000000..e096eb610475808df194e406d1932a543de723f3 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerCore.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerCore.h +/// @brief Core class of the QA checking framework (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @date 06.02.2023 + +#ifndef CbmQaCheckerCore_h +#define CbmQaCheckerCore_h 1 + +#include "CbmQaCheckerObjectDB.h" +#include "CbmQaCheckerTypedefs.h" + +#include "TFile.h" + +#include <bitset> +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +namespace cbm::qa::checker +{ + /// @brief Core class for CBM QA checker framework (declaration) + /// + /// Class CbmQaCheckerCore defines a core of the QA checker framework and provides a user interface for + /// the comparison routine execution + /// + class Core { + public: + /// @brief Default ctor + Core(); + + /// @brief Destructor + ~Core() = default; + + /// @brief Copy constructor + Core(const Core&) = delete; + + /// @brief Move constructor + Core(Core&&) = delete; + + /// @brief Copy assignment operator + Core& operator=(const Core&) = delete; + + /// @brief Move assignment operator + Core& operator=(Core&&) = delete; + + // ----- User interface + /// @brief Adds a version of QA output for a comparison + /// @param version Label of the version + /// @param path Path to the QA output directory for this version + void AddVersion(const char* version, const char* path); + + /// @brief Adds a dataset name + /// @param datasetName Name of dataset + void AddDataset(const char* datasetName); + + /// @brief Runs checking routine + /// @param opt Option: + /// "B": suppress canvas creation + /// "P": enables bin-by-bin comparison + /// "S": enables statistical hypothesis test, where is possible + /// "U": enables interval comparison, where is possible + void Process(Option_t* opt = "P"); + + /// @brief Registers root-file for storing output + /// @param filename Name of file + void RegisterOutFile(const char* filename); + + /// @brief Scans comparison results + /// @return Comparison flag: + /// - true: All histograms are the same under conditions + /// - false: Some of the histograms were changed + bool Scan() const; + + /// @brief Sets control flag + /// @param flag Flag label + /// @param value Flag value + void SetControlBitFlag(EFlagBit flag, bool value = true) { fControlBits[static_cast<int>(flag)] = value; } + + /// @brief Sets default version label + /// @param defaultLabel Name of default label + /// + /// If the default version is not provided as well as the provided, the first version will be used as the + /// default one. + void SetDefaultVersion(const char* defaultLabel) { fpObjDB->SetDefaultLabel(defaultLabel); } + + /// @brief Sets checker configuration from YAML file + /// @param configName Name of YAML configuration file + void SetFromYAML(const char* configName); + + /// @brief Sets root path to input files + /// @param pathName Relative or absolute root path to input the input directories + void SetInputRootPath(const char* pathName) { fpObjDB->SetInputRootPath(pathName); } + + private: + std::shared_ptr<TFile> fpOutFile = nullptr; ///< Output file + std::shared_ptr<ObjectDB> fpObjDB = nullptr; ///< Database of names + + FlagBitSet_t fControlBits; ///< Control bit flags + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerCore_h \ No newline at end of file diff --git a/core/qa/checker/CbmQaCheckerFileHandler.cxx b/core/qa/checker/CbmQaCheckerFileHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..a28a8c99f1b6011f6495b1303d5ba81e330077f9 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerFileHandler.cxx @@ -0,0 +1,196 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerFileHandler.cxx +/// @brief A handler class to process versions from similar files (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 08.02.2023 + +#include "CbmQaCheckerFileHandler.h" + +#include "CbmQaCheckerHist1DHandler.h" +#include "CbmQaCheckerHist2DHandler.h" +#include "CbmQaCheckerProfile1DHandler.h" + +#include "Logger.h" + +#include "TDirectoryFile.h" +#include "TFile.h" +#include "TFolder.h" +#include "TH1.h" +#include "TH2.h" +#include "TNamed.h" +#include "TProfile.h" + +#include <boost/algorithm/string.hpp> + +#include <iomanip> +#include <sstream> +#include <string> + + +using cbm::qa::checker::FileHandler; + +// --------------------------------------------------------------------------------------------------------------------- +// +FileHandler::FileHandler(std::shared_ptr<ObjectDB>& pObjDB, TDirectory* pOutDir, int iDataset, int iFile) + : fFileID(iFile) + , fDatasetID(iDataset) + , fpObjDB(pObjDB) + , fpOutDir(pOutDir) +{ + fpInputFiles = std::make_unique<TClonesArray>("TFile"); + + for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) { + std::string filename = fpObjDB->GetInputFileName(iVer, fFileID, fDatasetID); + auto* pInFile = new ((*fpInputFiles)[iVer]) TFile(filename.data(), "READONLY"); + LOG(info) << "File: " << pInFile->GetName(); + LOG_IF(fatal, !pInFile->IsOpen()) << "FileHandler: file " << filename << " cannot be opened"; + } + + // Check registered folder + LOG_IF(fatal, !fpObjDB.get()) << "FileHandler: attempt to register a null pointer for the object database"; + LOG_IF(fatal, !fpOutDir) << "FileHandler: attempt to register a null pointer for the output directory"; + LOG_IF(fatal, fDatasetID < 0) << "FileHandler: attempt to register undefined dataset index"; + LOG_IF(fatal, fFileID < 0) << "FileHandler: attempt to register undefined file index"; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +FileHandler::~FileHandler() +{ + // ----- Clean pointers + fpInputFiles->Delete(); + fpInputFiles = nullptr; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +TDirectory* FileHandler::CreateNestedDirectory(const std::string& path) +{ + fpOutDir->mkdir(path.data()); + return fpOutDir->GetDirectory(path.data()); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void FileHandler::Process(Option_t* opt) +{ + int nObjects = fpObjDB->GetNofObjects(fFileID); + int nVersions = fpObjDB->GetNofVersions(); + + // ----- Initial checks + if (!nObjects) { + LOG(warn) << "FileHandler: No objects were passed to file \"" << nObjects << ". Skipping file"; + return; + } + + // ----- Option parsing + std::string sOption = opt; + for (auto& ch : sOption) { + ch = std::tolower(ch); + } + bool bSuppressCanvases = sOption.find("b") != std::string::npos; + bool bCmpPointByPoint = sOption.find("p") != std::string::npos; + bool bCmpStatAny = sOption.find("s") != std::string::npos; + + LOG(info) << "FileHandler: processing objects: "; + std::vector<TNamed*> vpObjects(nVersions, nullptr); // vector to keep object different versions + for (int iObj = 0; iObj < nObjects; ++iObj) { + bool skipObj = false; + for (int iVer = 0; iVer < nVersions; ++iVer) { + auto* pInputFile = static_cast<TFile*>(fpInputFiles->At(iVer)); + vpObjects[iVer] = ReadObjectFromFile(pInputFile, fpObjDB->GetObject(fFileID, iObj)); + if (!vpObjects[iVer]) { + LOG(warn) << "FileHandler: object " << fpObjDB->GetObject(fFileID, iObj) << " is undefined for version " + << fpObjDB->GetVersionLabel(iVer) << ". This object will be skipped"; + skipObj = true; + } + } // iVer + if (skipObj) { continue; } + + // Create an instance of an object handler + std::unique_ptr<ObjectHandler> pObjHandler = nullptr; + LOG(info) << "FileHandler: processing object \"" << vpObjects[0]->GetName() << '\"'; + if (dynamic_cast<TH2*>(vpObjects[0])) { + pObjHandler = std::make_unique<Hist2DHandler>(iObj, fFileID, fDatasetID); + if (bCmpPointByPoint) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT); // Compare point-by-point (exact equality) + } + if (bCmpStatAny) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2); // Compare with chi2 test + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM); // Compare with Kolmogorov test + } + } + else if (dynamic_cast<TProfile*>(vpObjects[0])) { + pObjHandler = std::make_unique<Profile1DHandler>(iObj, fFileID, fDatasetID); + if (bCmpPointByPoint) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT); // Compare point-by-point (exact equality) + } + if (bCmpStatAny) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2); // Compare with chi2 test + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM); // Compare with Kolmogorov test + } + } + else if (dynamic_cast<TH1*>(vpObjects[0])) { + pObjHandler = std::make_unique<Hist1DHandler>(iObj, fFileID, fDatasetID); + if (bCmpPointByPoint) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kPOINT); // Compare point-by-point (exact equality) + } + if (bCmpStatAny) { + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kCHI2); // Compare with chi2 test + pObjHandler->SetBitFlag(Hist1DHandler::EFlags::kKOLM); // Compare with Kolmogorov test + } + } + else { + LOG(warn) << "FileHandler: Object " << fpObjDB->GetObject(fFileID, iObj) << " has a type \"" + << vpObjects[0]->ClassName() + << "\", which is unknown to the cbm::qa::checker framework, it will be skipped"; + continue; + } + pObjHandler->SetObjectDB(fpObjDB); + pObjHandler->SetOutputDirectory(CreateNestedDirectory((fpObjDB->GetObject(fFileID, iObj)))); + pObjHandler->AddObjects(vpObjects); + pObjHandler->CompareWithDefault(); + + // Create comparison canvas + if (!bSuppressCanvases) { + bool areDifferent = false; + for (int iVer = 0; iVer < nVersions; ++iVer) { + if (fpObjDB->GetCmpResult(fDatasetID, fFileID, iObj, iVer)) { areDifferent = true; } + } + if (areDifferent) { pObjHandler->CreateCanvases(); } + } + pObjHandler->Write(); + } // iObj +} + +// --------------------------------------------------------------------------------------------------------------------- +// +TNamed* FileHandler::ReadObjectFromFile(TFile* pFile, const std::string& path) const +{ + TObject* pObj = pFile; + size_t iPos = 0; // Index of first symbol + size_t iNext = 0; // Index of last symbol + while (iPos < path.size()) { + // get name + iNext = path.find_first_of('/', iPos); + if (iNext > path.size()) { iNext = path.size(); } + std::string part = path.substr(iPos, iNext - iPos); + iPos = iNext + 1; + if (!part.size()) { continue; } + + // test TDirectoryFile + if (dynamic_cast<TDirectoryFile*>(pObj)) { + auto* pDir = static_cast<TDirectoryFile*>(pObj); + pObj = pDir->FindObjectAny(part.data()); + } + // test TFolder + else if (dynamic_cast<TFolder*>(pObj)) { + auto* pDir = static_cast<TFolder*>(pObj); + pObj = pDir->FindObjectAny(part.data()); + } + } + return dynamic_cast<TNamed*>(pObj); +} diff --git a/core/qa/checker/CbmQaCheckerFileHandler.h b/core/qa/checker/CbmQaCheckerFileHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..0557ad3206d13231a301f98b83ab0753fc2e32bb --- /dev/null +++ b/core/qa/checker/CbmQaCheckerFileHandler.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerFileHandler.h +/// @brief A handler class to process versions from similar files (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 08.02.2023 + +#ifndef CbmQaCheckerFileHandler_h +#define CbmQaCheckerFileHandler_h 1 + +#include "CbmQaCheckerObjectDB.h" +#include "CbmQaCheckerTypedefs.h" + +#include "TClonesArray.h" + +#include <memory> +#include <vector> + +class TNamed; +class TFolder; +class TFile; + +namespace cbm::qa::checker +{ + /// @brief Handler for single files, created from different QA versions + /// + class FileHandler { + public: + /// @brief Constructor + /// @param pObjDB Shared pointer to object database + /// @param pOutDir Directory to store the output (/dataset/file) + /// @param iDataset Index of dataset + /// @param iFile Index of file + FileHandler(std::shared_ptr<ObjectDB>& pObjDB, TDirectory* pOutDir, int iDataset, int iFile); + + /// @brief Destructor + ~FileHandler(); + + /// @brief Copy constructor + FileHandler(const FileHandler&) = delete; + + /// @brief Move constructor + FileHandler(FileHandler&&) = delete; + + /// @brief Copy assignment operator + FileHandler& operator=(const FileHandler&) = delete; + + /// @brief Move assignment operator + FileHandler& operator=(FileHandler&&) = delete; + + /// @brief Gets index of dataset + /// @return Index of dataset + int GetDatasetID() const { return fDatasetID; } + + /// @brief Gets index of file + /// @return Index of file + int GetFileID() const { return fFileID; } + + /// @brief Processes comparison + /// @param opt Option: + /// "B": suppress canvas creation + /// "P": enables bin-by-bin comparison + /// "S": enables statistical hypothesis test, where is possible + /// "U": enables interval comparison + void Process(Option_t* opt = ""); + + private: + /// @brief Creates nested directory from a given path + /// @param path Path, parts of which are separated with slash + /// @return Pointer to created TDirectory object + TDirectory* CreateNestedDirectory(const std::string& path); + + /// @brief Reads object from file by the provided full path to the object. + /// @param pFile Pointer to TFile instance + /// @param path Full path to the object inside the file + /// @return A pointer to TNamed object + TNamed* ReadObjectFromFile(TFile* pFile, const std::string& path) const; + + + int fFileID = -1; ///< Index of file + int fDatasetID = -1; ///< Index of dataset + + std::shared_ptr<ObjectDB> fpObjDB = nullptr; ///< Pointer to object database + TDirectory* fpOutDir = nullptr; ///< Pointer to output directory + std::unique_ptr<TClonesArray> fpInputFiles = nullptr; ///< Pointer to input files array + + // TODO: replace fpOutputFolder with shared_ptr + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerFileHandler_h diff --git a/core/qa/checker/CbmQaCheckerHist1DHandler.cxx b/core/qa/checker/CbmQaCheckerHist1DHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..16312a18088970ee1f7ac1351a498de599b00cb6 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerHist1DHandler.cxx @@ -0,0 +1,203 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerHist1DHandler.cxx +/// @brief Handler class for 1D-histograms (including TProfile objects) (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 09.02.2023 + +#include "CbmQaCheckerHist1DHandler.h" + +#include "CbmQaCanvas.h" +#include "CbmQaCheckerTypedefs.h" + +#include "Logger.h" + +#include "TCanvas.h" +#include "TError.h" +#include "TFolder.h" +#include "TGraphAsymmErrors.h" +#include "TH1.h" +#include "TLegend.h" +#include "TMath.h" +#include "TMultiGraph.h" +#include "TObject.h" +#include "TStyle.h" + +#include <bitset> +#include <limits> + +using cbm::qa::checker::CmpResult_t; +using cbm::qa::checker::Hist1DHandler; + +// --------------------------------------------------------------------------------------------------------------------- +// +Hist1DHandler::Hist1DHandler(int iObject, int iFile, int iDataset) : ObjectHandler(iObject, iFile, iDataset, "TH1") {} + +// --------------------------------------------------------------------------------------------------------------------- +// + + +// --------------------------------------------------------------------------------------------------------------------- +// +CmpResult_t Hist1DHandler::Compare(const TNamed* pNum, const TNamed* pDenom) const +{ + std::bitset<sizeof(CmpResult_t) * 8> res; + auto* pNumerator = static_cast<const TH1*>(pNum); + auto* pDenominator = static_cast<const TH1*>(pDenom); + if (fOptionBits[EFlags::kPOINT] && ComparePointToPoint(pNumerator, pDenominator)) { res[EFlags::kPOINT] = true; } + if (fOptionBits[EFlags::kRATIO] && CompareRatioDeviation(pNumerator, pDenominator)) { res[EFlags::kRATIO] = true; } + if (fOptionBits[EFlags::kCHI2] && CompareWithChi2(pNumerator, pDenominator)) { res[EFlags::kCHI2] = true; } + if (fOptionBits[EFlags::kKOLM] && CompareWithKolmogorov(pNumerator, pDenominator)) { res[EFlags::kKOLM] = true; } + return res.to_ulong(); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Hist1DHandler::CreateCanvases() +{ + int nVersions = fpObjDB->GetNofVersions(); + int iDef = fpObjDB->GetDefaultID(); + + // ----- Canvas: comparison of original histograms, ratios and subtractions + std::string sCanvName = fsBaseName + "_cmp_canvas"; + std::string sCanvTitle = "Comparison result for \"" + fsBaseName + "\""; + fpCanvas = std::make_shared<CbmQaCanvas>(sCanvName.data(), sCanvTitle.data(), 1500, 500); + fpCanvas->cd(); + auto* pPadOrig = new TPad("p1", "p1", 0.0, 0.0, 0.3333, 1.0); + pPadOrig->SetMargin(0.20, 0.05, 0.20, 0.10); + fpCanvas->cd(); + auto* pPadRatio = new TPad("p2", "p2", 0.3333, 0.0, 0.6666, 1.0); + pPadRatio->SetMargin(0.20, 0.05, 0.20, 0.10); + fpCanvas->cd(); + auto* pPadDiff = new TPad("p3", "p3", 0.6666, 0.0, 1.0, 1.0); + pPadDiff->SetMargin(0.20, 0.05, 0.20, 0.10); + + // Title definitions + const char* title = fvpObjects[0]->GetTitle(); + const char* titleRatio = Form("Ratio to %s", fpObjDB->GetVersionLabel(iDef).data()); + const char* titleDiff = Form("Difference from %s", fpObjDB->GetVersionLabel(iDef).data()); + const char* xAxisTitle = static_cast<TH1*>(fvpObjects[0])->GetXaxis()->GetTitle(); + const char* yAxisTitle = static_cast<TH1*>(fvpObjects[0])->GetYaxis()->GetTitle(); + + // Original histograms + pPadOrig->cd(); + auto* pMultiGraphOrig = new TMultiGraph(fsBaseName.data(), title); + for (int iV = 0; iV < nVersions; ++iV) { + auto* pCopy = new TGraphAsymmErrors((TH1*) fvpObjects[iV]); + pCopy->SetMarkerStyle(20); + pCopy->SetTitle(fpObjDB->GetVersionLabel(iV).data()); + pMultiGraphOrig->Add(pCopy, "P"); + } + pMultiGraphOrig->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphOrig->GetYaxis()->SetTitle(yAxisTitle); + pMultiGraphOrig->Draw("A pmc plc"); + pPadOrig->BuildLegend(); + + auto* pDefault = static_cast<TH1*>(fvpObjects[iDef]); + + // Histogram ratios to default + pPadRatio->cd(); + auto* pMultiGraphRatio = new TMultiGraph((fsBaseName + "_ratio").data(), titleRatio); + for (int iV = 0; iV < nVersions; ++iV) { + if (iV == iDef) { continue; } + auto* pRatio = static_cast<TH1*>(fvpObjects[iV]->Clone()); + pRatio->SetDirectory(fpOutDir); + auto currErrorLevel = gErrorIgnoreLevel; + gErrorIgnoreLevel = kError; + auto* pCopy = new TGraphAsymmErrors(pRatio, pDefault, "pois"); + gErrorIgnoreLevel = currErrorLevel; + pCopy->SetMarkerStyle(20); + pMultiGraphRatio->Add(pCopy, "P"); + if (pRatio) { + delete pRatio; + pRatio = nullptr; + } + } + pMultiGraphRatio->GetYaxis()->SetTitle("ratio"); + pMultiGraphRatio->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphRatio->Draw("A pmc plc"); + + // Histogram ratios to default + pPadDiff->cd(); + auto* pMultiGraphDiff = new TMultiGraph((fsBaseName + "_diff").data(), titleDiff); + for (int iV = 0; iV < nVersions; ++iV) { + if (iV == iDef) { continue; } + auto* pDiff = static_cast<TH1*>(fvpObjects[iV]->Clone()); + pDiff->SetDirectory(fpOutDir); + pDiff->Add(pDefault, -1.); + auto* pCopy = new TGraphAsymmErrors(pDiff); + pCopy->SetMarkerStyle(20); + pMultiGraphDiff->Add(pCopy, "P"); + if (pDiff) { + delete pDiff; + pDiff = nullptr; + } + } + pMultiGraphDiff->GetYaxis()->SetTitle("difference"); + pMultiGraphDiff->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphDiff->Draw("A pmc plc"); + fpCanvas->cd(); + pPadOrig->Draw(); + pPadRatio->Draw(); + pPadDiff->Draw(); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Hist1DHandler::ComparePointToPoint(const TH1* pNumerator, const TH1* pDenominator) +{ + bool ifDifferent = false; + // Compare content and error of each bin + // TODO: write a comment about + for (int iBinX = 0; iBinX <= pNumerator->GetNbinsX(); ++iBinX) { + for (int iBinY = 0; iBinY <= pNumerator->GetNbinsY(); ++iBinY) { + for (int iBinZ = 0; iBinZ <= pNumerator->GetNbinsZ(); ++iBinZ) { + auto numBinContent = pNumerator->GetBinContent(iBinX, iBinY, iBinZ); + auto denBinContent = pDenominator->GetBinContent(iBinX, iBinY, iBinZ); + // Check bin content + if (!TMath::IsNaN(numBinContent) && !TMath::IsNaN(denBinContent)) { + if (numBinContent != denBinContent) { return true; } + } + else { + if (TMath::IsNaN(numBinContent) != TMath::IsNaN(denBinContent)) { return true; } + } + auto numBinError = pNumerator->GetBinError(iBinX, iBinY, iBinZ); + auto denBinError = pDenominator->GetBinError(iBinX, iBinY, iBinZ); + // Check bin error + if (!TMath::IsNaN(numBinError) && !TMath::IsNaN(denBinError)) { + if (numBinError != denBinError) { return true; } + } + else { + if (TMath::IsNaN(numBinError) != TMath::IsNaN(denBinError)) { return true; } + } + } + } + } + return ifDifferent; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Hist1DHandler::CompareRatioDeviation(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/, double /*allowedDev*/) +{ + LOG(warn) << "Hist1DHandler::CompareRatioDeviation function was not implemented"; + return false; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Hist1DHandler::CompareWithChi2(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/) +{ + LOG(warn) << "Hist1DHandler::CompareWthChi2 function was not implemented"; + return false; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Hist1DHandler::CompareWithKolmogorov(const TH1* /*pNumerator*/, const TH1* /*pDenominator*/) +{ + LOG(warn) << "Hist1DHandler::CompareWithKolmogorov function was not implemented"; + return false; +} diff --git a/core/qa/checker/CbmQaCheckerHist1DHandler.h b/core/qa/checker/CbmQaCheckerHist1DHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..de0c828e5db1081257fc8c26dac6d86d2a5497e5 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerHist1DHandler.h @@ -0,0 +1,100 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerHist1DHandler.h +/// @brief Handler class for 1D-histograms (including TProfile objects) (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 09.02.2023 + +#ifndef CbmQaCheckerHist1DHandler_h +#define CbmQaCheckerHist1DHandler_h 1 + +#include "CbmQaCheckerObjectHandler.h" + +#include <memory> +#include <string> + +class TObject; +class TH1; + +namespace cbm::qa::checker +{ + /// @brief Handler for 1D-histograms. + /// + /// The handler keeps one-dimensional histogram objects of the same quantity within different code versions + /// and provides several comparison methods including point-by-point comparison, test of ratio deviation and + /// statistical hypothesis tests (Chi2 test and Kolmogorov test). + /// + class Hist1DHandler : public ObjectHandler { + public: + /// Enumeration for bit-flags of different comparison methods + enum EFlags + { + kPOINT, ///< Point-by-point exact comparison + kRATIO, ///< Comparison within of ratio difference from unity + kCHI2, ///< Statistical hypothesis test using chi2 test + kKOLM ///< Statistical hypothesis test using Kolmogorov test + }; + + /// @brief Constructor + /// @param iObject Index of object + /// @param iFile Index of file + /// @param iDataset Index of dataset + Hist1DHandler(int iObject, int iFile, int iDataset); + + /// @brief Destructor + ~Hist1DHandler() = default; + + /// @brief Creates object comparison canvas + void CreateCanvases(); + + /// @brief Compares objects to default + /// @param pNum Pointer to "numerator" object + /// @param pDenom Pointer to "denominator" object + /// @return Comparison result represented as a bitset in unsigned integer + /// + /// The function provides comparison of objects and stores the comparison result in the field of the ObjectDB + /// instance for a given version, file, object and dataset ids. The comparison result is represented with the + /// unsigned integer, each raised bit of which declares, that the objects are different within the corresponding + /// comparison method. + /// Bits used: + /// 0: Point to point comparison + /// 1: Comparison based on the ratio deviation + /// 2: Statistical test comparison with chi2-test + /// 3: Statistical test comparison with Kolmogorov test + CmpResult_t Compare(const TNamed* pNum, const TNamed* pDenom) const; + + private: + /// @brief Compares two histograms point-to-point + /// @param pNumerator Pointer to the numerator object of comparison (usually new version) + /// @param pDenominator Pointer to the denominator object for comparison (usually default) + /// @return true Histograms are different + /// @return false Histograms are equal + static bool ComparePointToPoint(const TH1* pNumerator, const TH1* pDenominator); + + /// @brief Compares two histograms by maximum deviation of the ratio + /// @param pNumerator Pointer to the numerator object of comparison (usually new version) + /// @param pDenominator Pointer to the denominator object for comparison (usually default) + /// @param allowedDev Max allowed ratio deviation from unity + /// @return true Histograms are different + /// @return false Histograms are equal + static bool CompareRatioDeviation(const TH1* pNumerator, const TH1* pDenominator, double allowedDev = 10); + + /// @brief Compares two histograms with chi2-test + /// @param pNumerator Pointer to the numerator object of comparison (usually new version) + /// @param pDenominator Pointer to the denominator object for comparison (usually default) + /// @return true Histograms are different + /// @return false Histograms are equal + static bool CompareWithChi2(const TH1* pNumerator, const TH1* pDenominator); + + /// @brief Compares two histograms with Kolmogorov test + /// @param pNumerator Pointer to the numerator object of comparison (usually new version) + /// @param pDenominator Pointer to the denominator object for comparison (usually default) + /// @return true Histograms are different + /// @return false Histograms are equal + static bool CompareWithKolmogorov(const TH1* pNumerator, const TH1* pDenominator); + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerHist1DHandler_h diff --git a/core/qa/checker/CbmQaCheckerHist2DHandler.cxx b/core/qa/checker/CbmQaCheckerHist2DHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..e0ee23f9c3ee9988fc34180161b1780d991f64e8 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerHist2DHandler.cxx @@ -0,0 +1,16 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerHist2DHandler.h +/// @brief Handler class for 2D-histograms (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 21.02.2023 + +#include "CbmQaCheckerHist2DHandler.h" + +using cbm::qa::checker::Hist2DHandler; + +// --------------------------------------------------------------------------------------------------------------------- +// +Hist2DHandler::Hist2DHandler(int iObject, int iFile, int iDataset) : Hist1DHandler(iObject, iFile, iDataset) {} diff --git a/core/qa/checker/CbmQaCheckerHist2DHandler.h b/core/qa/checker/CbmQaCheckerHist2DHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..fe3ce9b7a023e2c8d1aa2ac7fe87089c6da8c2ce --- /dev/null +++ b/core/qa/checker/CbmQaCheckerHist2DHandler.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerHist2DHandler.h +/// @brief Handler class for 2D-histograms (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 21.02.2023 + +#ifndef CbmQaCheckerHist2DHandler_h +#define CbmQaCheckerHist2DHandler_h 1 + +#include "CbmQaCheckerHist1DHandler.h" + +namespace cbm::qa::checker +{ + /// @brief Specification of the handler for TProfile class + /// + class Hist2DHandler : public Hist1DHandler { + public: + /// @brief Constructor + /// @param iObject Index of object + /// @param iFile Index of file + /// @param iDataset Index of dataset + Hist2DHandler(int iObject, int iFile, int iDataset); + + /// @brief Destructor + ~Hist2DHandler() = default; + + /// @brief Creates object comparison canvas + void CreateCanvases() {}; + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerProfile1DHandler_h diff --git a/core/qa/checker/CbmQaCheckerObjectDB.cxx b/core/qa/checker/CbmQaCheckerObjectDB.cxx new file mode 100644 index 0000000000000000000000000000000000000000..8625f2b7915efd2e1bbfd9298307ccdf8449dc07 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerObjectDB.cxx @@ -0,0 +1,255 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerObjectDB.h +/// @brief Database for processed objects in the QA checker framework (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 15.02.2023 + +#include "CbmQaCheckerObjectDB.h" + +#include "Logger.h" + +#include <algorithm> +#include <regex> +#include <sstream> + +using cbm::qa::checker::ObjectDB; + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectDB::Clear() +{ + fvDatasets.clear(); + fvFiles.clear(); + fvFileLabels.clear(); + fvObjects.clear(); + fvVersionLabels.clear(); + fvVersionPaths.clear(); + fvGlobalToFileObject.clear(); + fvCmpResults.clear(); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +// ObjectDB::GetObjects(int iFile) const +// { +// auto itBegin = std::upper_bound(fvGlobalToFileObject.begin(), fvGlobalToFileObject.end(), iFile, +// [](int i, const std::pair<int, int>& p) { return i <= p.first; }); +// auto itEnd = std::lower_bound(itBegin, fvGlobalToFileObject.end(), iFile, +// [](const std::pair<int, int>& p, int i) { return i >= p.first; }); +// +// int iBegin = itBegin - fvGlobalToFileObject.begin(); +// int iEnd = itEnd - fvGlobalToFileObject.begin(); +// return boost::make_iterator_range(fvObjects.begin() + iBegin, fvObjects.begin() + iEnd); +// } + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectDB::AddVersion(const char* label, const char* path) +{ + fvVersionLabels.push_back(label); + fvVersionPaths.push_back(path); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectDB::AddDataset(const char* dataset) { fvDatasets.push_back(dataset); } + +// --------------------------------------------------------------------------------------------------------------------- +// +std::string ObjectDB::GetInputFileName(int iVersion, int iFile, int iDataset) const +{ + std::string res = fvFiles[iFile]; + res = std::regex_replace(res, std::regex("\\%v"), fvVersionPaths[iVersion]); + res = std::regex_replace(res, std::regex("\\%d"), fvDatasets[iDataset]); + return res; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectDB::Init() +{ + // ----- Check consistency of input values + LOG_IF(fatal, !GetNofObjects()) << "ObjectDB: No objects were passed to the checker"; + LOG_IF(fatal, GetNofDatasets() < 1) << "ObjectDB: No datasets were founde, at least one dataset should be provided"; + LOG_IF(fatal, GetNofVersions() < 2) << "ObjectDB: File handler should have at least two versions to compare (" + << GetNofVersions() << " were provided)"; + + + // ----- Define default version index + if (fsDefaultLabel.size()) { + auto it = std::find(fvVersionLabels.cbegin(), fvVersionLabels.cend(), fsDefaultLabel); + if (it == fvVersionLabels.cend()) { + std::stringstream msg; + msg << "ObjectDB: registered default label \"" << fsDefaultLabel << "\" is not found among the version labels:\n"; + for (const auto& label : fvVersionLabels) { + msg << "\t- " << label << '\n'; + } + LOG(fatal) << msg.str(); + } + fDefVersionID = it - fvVersionLabels.cbegin(); + } + else { + fDefVersionID = 0; + LOG(warn) << "ObjectDB: default version was not registered. Using the first version as the default one (\"" + << fvVersionLabels[fDefVersionID] << "\")"; + } + + // ----- Reserve space for object comparison results + fvCmpResults.resize(fvVersionLabels.size() * fvObjects.size() * fvDatasets.size()); + + // ----- Add root path of input, if it were defined + auto regexSlashes = std::regex("(/+)"); // regular expression for a sequence of consecutive slashes + for (auto& path : fvVersionPaths) { + if (fsInputRootPath.size()) { path = fsInputRootPath + "/" + path; } + path = std::regex_replace(path, regexSlashes, "/"); // replace all consecutive slashes with a single one + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectDB::ReadFromYAML(const char* configName) +{ + // ----- Open input file + YAML::Node config; + try { + config = YAML::LoadFile(configName)["checker"]; + } + catch (const YAML::BadFile* exc) { + LOG(fatal) << "ObjectDB: configuration file " << configName << " does not exist"; + } + catch (const YAML::ParserException& exc) { + LOG(fatal) << "ObjectDB: configuration file " << configName << " is badly formatted"; + } + + // ----- Define file-object map + if (config["files"]) { + if (fvGlobalToFileObject.size()) { + LOG(warn) << "ObjectDB: file-object map was defined before. Redefining it from the config file " << configName; + fvGlobalToFileObject.clear(); + fvFiles.clear(); + fvFileLabels.clear(); + fvObjects.clear(); + } + try { + const auto& rootNode = config["files"]; + + // Calculate total number of objects and files + size_t nFiles = rootNode.size(); + size_t nObjects = 0; + for (const auto& fileNode : rootNode) { + nObjects += fileNode["objects"].size(); + } + fvFiles.reserve(nFiles); + fvFileLabels.reserve(nFiles); + fvObjects.reserve(nObjects); + fvGlobalToFileObject.reserve(nObjects); + + // Fill vectors + for (const auto& fileNode : rootNode) { + size_t iObj = 0; + for (const auto& objectNode : fileNode["objects"]) { + fvGlobalToFileObject.emplace_back(fvFiles.size(), iObj++); + fvObjects.push_back(objectNode.as<std::string>()); + } + fvFiles.push_back(fileNode["name"].as<std::string>()); + fvFileLabels.push_back(fileNode["label"].as<std::string>()); + } + } + catch (const YAML::InvalidNode& exc) { + LOG(fatal) << "ObjectDB: error while reading checker/files node from the config " << configName; + } + } + else { + LOG(warn) << "ObjectDB: node checker/inputformat is not defined in the config " << configName; + } + + // ----- Define dataset names + if (config["datasets"]) { + if (fvDatasets.size()) { + LOG(warn) << "ObjectDB: dataset names were defined before. Redefining them from the config " << configName; + fvDatasets.clear(); + } + try { + const auto& rootNode = config["datasets"]; + fvDatasets.reserve(rootNode.size()); + for (const auto& datasetNode : rootNode) { + fvDatasets.push_back(datasetNode.as<std::string>()); + } + } + catch (const YAML::InvalidNode& exc) { + LOG(fatal) << "ObjectDB:: error while reading checker/datasets node from the config " << configName; + } + } + else { + LOG(warn) << "ObjectDB: node checker/inputformat is not defined in the config " << configName; + } + + // ----- Define version names + if (config["versions"]) { + if (fvVersionLabels.size()) { + LOG(warn) << "ObjectDB: dataset names were defined before. Redefining them from the config " << configName; + fvVersionLabels.clear(); + fvVersionPaths.clear(); + } + try { + const auto& rootNode = config["versions"]; + fvVersionLabels.reserve(rootNode.size()); + fvVersionPaths.reserve(rootNode.size()); + for (const auto& versionNode : rootNode) { + fvVersionLabels.push_back(versionNode["label"].as<std::string>()); + fvVersionPaths.push_back(versionNode["path"].as<std::string>()); + } + } + catch (const YAML::InvalidNode& exc) { + LOG(fatal) << "ObjectDB:: error while reading checker/versions node from the config " << configName; + } + } + else { + LOG(warn) << "ObjectDB: node checker/versions is not defined in the config " << configName; + } + + // ----- Define default version + if (config["default_label"]) { + try { + SetDefaultLabel(config["default_label"].as<std::string>().data()); + } + catch (const YAML::InvalidNode& exc) { + LOG(fatal) << "ObjectDB:: error while reading checker/default_label node from the config " << configName; + } + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// +std::string ObjectDB::ToString() const +{ + std::stringstream msg; + msg << "*** CBM QA-Checker: defied files ****\n\n"; + + msg << "----- Versions:\n"; + for (size_t iV = 0; iV < fvVersionLabels.size(); ++iV) { + if (iV == (size_t) fDefVersionID) { + msg << "\033[1;32m- " << fvVersionLabels[iV] << " (path: " << fvVersionPaths[iV] << ")\n ---> DEFAULT\033[0m"; + } + else { + msg << "- " << fvVersionLabels[iV] << " (path: " << fvVersionPaths[iV] << ")\n"; + } + } + + msg << "----- Datasets:\n"; + for (const auto& dataset : fvDatasets) { + msg << "- \033[1m" << dataset << "\033[0m\n"; + } + + msg << "----- Objects\n"; + for (size_t iF = 0; iF < fvFiles.size(); ++iF) { + msg << "- \033[1m" << fvFiles[iF] << "\033[0m\n"; + for (const auto& object : this->GetObjects(iF)) { + msg << "\t- " << object << '\n'; + } + } + return msg.str(); +} diff --git a/core/qa/checker/CbmQaCheckerObjectDB.h b/core/qa/checker/CbmQaCheckerObjectDB.h new file mode 100644 index 0000000000000000000000000000000000000000..49eea504b998fe4fa7d89f94325cc7ca36b8351c --- /dev/null +++ b/core/qa/checker/CbmQaCheckerObjectDB.h @@ -0,0 +1,219 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerObjectDB.h +/// @brief Database for processed objects in the QA checker framework (header) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 15.02.2023 + +#ifndef CbmQaCheckerObjectDB_h +#define CbmQaCheckerObjectDB_h 1 + +#include "CbmQaCheckerTypedefs.h" + +#include "Logger.h" + +#include <string> +#include <utility> +#include <vector> + +#include <yaml-cpp/yaml.h> + +// TMP +#include <boost/core/demangle.hpp> + +#include <typeinfo> +template<typename T> +std::string type_str() +{ + return boost::core::demangle(typeid(T).name()); +} + +namespace cbm::qa::checker +{ + /// @brief A data base class for processed objects + /// + /// The data base contains information on datasets, file-object pairs and versions, which are compared. + class ObjectDB { + public: + /// @brief Default constructor + ObjectDB() = default; + + /// @brief Destructor + ~ObjectDB() = default; + + /// @brief Copy constructor + ObjectDB(const ObjectDB&) = delete; + + /// @brief Move constructor + ObjectDB(ObjectDB&&) = delete; + + /// @brief Copy assignment operator + ObjectDB& operator=(const ObjectDB&) = delete; + + /// @brief Move assignment operator + ObjectDB& operator=(ObjectDB&&) = delete; + + /// @brief Adds version + /// @param label Label of version + /// @param path Path to output files made for this version + void AddVersion(const char* label, const char* path); + + /// @brief Adds dataset + /// @param dataset Name of dataset + void AddDataset(const char* dataset); + + /// @brief Clears content + void Clear(); + + /// @brief Gets name of dataset + /// @param iDataset Index of dataset + /// @return Name of dataset + const auto& GetDataset(int iDataset) const { return fvDatasets[iDataset]; } + + /// @brief Gets reference to dataset name array + const auto& GetDatasets() const { return fvDatasets; } + + /// @brief Gets index of default version + /// @return Index of default version + int GetDefaultID() const { return fDefVersionID; } + + /// @brief Gets label of the default version + const std::string& GetDefaultLabel() const + { + assert(fDefVersionID > -1); + return GetVersionLabel(fDefVersionID); + } + + /// @brief Gets label of file + /// @param iFile Index of file + /// @return Label of file + const auto& GetFileLabel(int iFile) const { return fvFileLabels[iFile]; } + + /// @brief Gets iterator range for object names stored in a file + /// @note The iterator_range object behaves itself just like an ordinary STL container. + /// @param iFile Index of file + /// @return iterator_range for ROOT objects of this file + /// TODO: Understand, which type is deduced + auto GetObjects(int iFile) const + { + auto itBegin = std::upper_bound(fvGlobalToFileObject.begin(), fvGlobalToFileObject.end(), iFile, + [](int i, const std::pair<int, int>& p) { return i <= p.first; }); + auto itEnd = std::lower_bound(itBegin, fvGlobalToFileObject.end(), iFile, + [](const std::pair<int, int>& p, int i) { return i >= p.first; }); + + int iBegin = itBegin - fvGlobalToFileObject.begin(); + int iEnd = itEnd - fvGlobalToFileObject.begin(); + return boost::make_iterator_range(fvObjects.begin() + iBegin, fvObjects.begin() + iEnd); + } + + /// @brief Gets comparison result + /// @param iDS Index of dataset + /// @param iFile Index of file + /// @param iObj Index of object within the file + /// @param iVer Index of version + /// @return Value of comparison result + CmpResult_t GetCmpResult(int iDS, int iFile, int iObj, int iVer) const + { + int iObjGlob = GetObjects(iFile).begin() - fvObjects.begin() + iObj; + int iRes = iVer + fvVersionLabels.size() * (iObjGlob + iDS * fvObjects.size()); + return fvCmpResults[iRes]; + } + + /// @brief Gets name of file from indexes of version, file and dataset + /// @param iVersion Index of version + /// @param iFile Index of file + /// @param iDataset Index of dataset + /// @return Name of input file + std::string GetInputFileName(int iVersion, int iFile, int iDataset) const; + + /// @brief Gets object name by its local index and index of file + /// @param iFile Index of file + /// @param iObject Index of object for a given file + /// @return Name of object + const std::string& GetObject(int iFile, int iObject) const { return GetObjects(iFile)[iObject]; } + + /// @brief Gets number of datasets + int GetNofDatasets() const { return fvDatasets.size(); } + + /// @brief Gets total number of objects + int GetNofObjects() const { return fvObjects.size(); } + + /// @brief Gets number of objects in file + /// @param iFile Index of file + int GetNofObjects(int iFile) const { return GetObjects(iFile).size(); } + + /// @brief Gets number of files + int GetNofFiles() const { return fvFiles.size(); } + + /// @brief Gets number of versions + int GetNofVersions() const { return fvVersionLabels.size(); } + + /// @brief Gets version label + /// @param iVersion Index of version + const std::string& GetVersionLabel(int iVersion) const { return fvVersionLabels[iVersion]; } + + /// @brief Gets reference to version label array + const auto& GetVersionLabels() const { return fvVersionLabels; } + + /// @brief Gets version path + /// @param iVersion Index of version + const std::string& GetVersionPath(int iVersion) const { return fvVersionPaths[iVersion]; } + + /// @brief Gets reference to version path array + const auto& GetVersionPaths() const { return fvVersionPaths; } + + /// @brief Initializes the database + void Init(); + + /// @brief Reads DB from YAML node + /// @param config Root node of the YAML file + void ReadFromYAML(const char* configName); + + /// @brief Saves content to string + /// @return A string representation of the DB contents + std::string ToString() const; + + /// @brief Sets comparison result + /// @param iDS Index of dataset + /// @param iFile Index of file + /// @param iObj Index of object within the file + /// @param iVer Index of version + /// @param value Value of comparison result + void SetCmpResult(int iDS, int iFile, int iObj, int iVer, CmpResult_t value) + { + int iObjGlob = GetObjects(iFile).begin() - fvObjects.begin() + iObj; + int iRes = iVer + fvVersionLabels.size() * (iObjGlob + iDS * fvObjects.size()); + fvCmpResults[iRes] = value; + } + + /// @brief Sets default version label + /// @param defaultLabel Name of default label + /// + /// If the default version is not provided as well as the provided, the first version will be used as the + /// default one. + void SetDefaultLabel(const char* defaultLabel) { fsDefaultLabel = defaultLabel; } + + /// @brief Sets root path to input files + /// @param pathName Relative or absolute root path to input the input directories + void SetInputRootPath(const char* pathName) { fsInputRootPath = pathName; } + + private: + int fDefVersionID = -1; ///< Index of default version + + std::string fsInputRootPath = ""; ///< Root path for input files + std::string fsDefaultLabel = ""; ///< Name of default version label + + std::vector<std::string> fvDatasets; ///< Container of dataset names + std::vector<std::string> fvFiles; ///< Container of file names + std::vector<std::string> fvFileLabels; ///< Container of file labels (used in output) + std::vector<std::string> fvObjects; ///< Container of object names (joint for all the files) + std::vector<std::string> fvVersionLabels; ///< Container of version labels + std::vector<std::string> fvVersionPaths; ///< Container of version paths + std::vector<std::pair<int, int>> fvGlobalToFileObject; ///< Map of global obj. index -> local file-object + std::vector<CmpResult_t> fvCmpResults; ///< Comparison results vs. dataset, object and version + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerObjectDB_h diff --git a/core/qa/checker/CbmQaCheckerObjectHandler.cxx b/core/qa/checker/CbmQaCheckerObjectHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7ed93e87b9fe342c4f88bf5e2504aa17200b2f3b --- /dev/null +++ b/core/qa/checker/CbmQaCheckerObjectHandler.cxx @@ -0,0 +1,88 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerObjectHandler.h +/// @brief Base handler class (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 09.02.2023 + +#include "CbmQaCheckerObjectHandler.h" + +#include "CbmQaCanvas.h" + +#include "Logger.h" + +#include "TDirectory.h" +#include "TNamed.h" + +using cbm::qa::checker::ObjectHandler; + +// --------------------------------------------------------------------------------------------------------------------- +// +ObjectHandler::ObjectHandler(int iObject, int iFile, int iDataset, const char* objType) + : fObjectID(iObject) + , fFileID(iFile) + , fDatasetID(iDataset) + , fsObjType(objType) +{ +} + +// --------------------------------------------------------------------------------------------------------------------- +// +ObjectHandler::~ObjectHandler() {} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectHandler::AddObjects(const std::vector<TNamed*>& vpObj) +{ + // ----- Check input + LOG_IF(fatal, !fpObjDB) << "ObjectHandler: object database was not defined"; + LOG_IF(fatal, !fpOutDir) << "ObjectHandler: output directory was not defined"; + LOG_IF(fatal, (int) vpObj.size() != fpObjDB->GetNofVersions()) + << "ObjectHandler: Attempt to add vector with object pointers of different to the one of version labels"; + + TDirectory* pCurrDir = gDirectory; + gDirectory = fpOutDir; + for (int iVer = 0; iVer < fpObjDB->GetNofVersions(); ++iVer) { + if (!fsBaseName.size()) { fsBaseName = vpObj[iVer]->GetName(); } + else { + LOG_IF(fatal, strcmp(fsBaseName.c_str(), vpObj[iVer]->GetName())) + << "Hist1DHandler: attempt to add object of different name " << fsBaseName << " vs. " << vpObj[iVer]->GetName(); + } + const char* cloneName = Form("%s_orig_%s", fsBaseName.data(), fpObjDB->GetVersionLabel(iVer).data()); + fvpObjects.push_back((TNamed*) vpObj[iVer]->Clone(cloneName)); + } + gDirectory = pCurrDir; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectHandler::CompareWithDefault() +{ + int iDef = fpObjDB->GetDefaultID(); + for (int iV = 0; iV < (int) fvpObjects.size(); ++iV) { + if (iV == iDef) { continue; } + auto res = this->Compare(fvpObjects[iV], fvpObjects[iDef]); + fpObjDB->SetCmpResult(fDatasetID, fFileID, fObjectID, iV, res); + } +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectHandler::SetOutputDirectory(TDirectory* pDir) +{ + LOG_IF(fatal, !pDir) << "ObjectHandler: attempt to pass nullptr as a folder"; + fpOutDir = pDir; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void ObjectHandler::Write() +{ + fpOutDir->cd(); + for (auto pObj : fvpObjects) { + pObj->Write(); + } + if (fpCanvas.get()) { fpCanvas->Write(); } +} \ No newline at end of file diff --git a/core/qa/checker/CbmQaCheckerObjectHandler.h b/core/qa/checker/CbmQaCheckerObjectHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..1ad5fd824caf2aa42db1deb3f40eb6a27c66eb54 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerObjectHandler.h @@ -0,0 +1,102 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerObjectHandler.h +/// @brief Base handler class (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 08.02.2023 + +#ifndef CbmQaCheckerObjectHandler_h +#define CbmQaCheckerObjectHandler_h 1 + +#include "CbmQaCheckerObjectDB.h" +#include "CbmQaCheckerTypedefs.h" + +#include "Rtypes.h" + +#include <memory> +#include <string> + +class TDirectory; +class TNamed; +class TObject; +class TCanvas; +class CbmQaCanvas; + +namespace cbm::qa::checker +{ + /// @brief Base abstract class for object handler. + /// + /// The class provides interface for handling objects of the same type, obtained under different versions of the code + /// base. + /// + class ObjectHandler { + public: + /// @brief Default constructor + /// @param iObject Index of object + /// @param iFile Index of file + /// @param iDataset Index of dataset + /// @param objType Type of the handled objects + ObjectHandler(int iObject, int iFile, int iDataset, const char* objType = ""); + + /// @brief Destructor + virtual ~ObjectHandler(); + + /// @brief Adds vector of pointer to objects + /// @param vpObj Vector of pointers to TNamed objects + void AddObjects(const std::vector<TNamed*>& vpObj); + + /// @brief Creates object comparison canvas + virtual void CreateCanvases() {}; + + /// @brief Compares two objects with different methods + /// @param pNum Pointer to "numerator" object + /// @param pDenom Pointer to "denominator" object + /// @return Comparison result represented as a bitset in unsigned integer + /// TODO: ..... + virtual CmpResult_t Compare(const TNamed* pNum, const TNamed* pDenom) const = 0; + + /// @brief Compares versions with default and writes result into DB + void CompareWithDefault(); + + /// @brief Sets folder to store output + /// @param pDir Pointer to folder instance + void SetOutputDirectory(TDirectory* pDir); + + /// @brief Sets objects database + /// @param pObjDB Shared pointer to object database + void SetObjectDB(std::shared_ptr<ObjectDB>& pObjDB) { fpObjDB = pObjDB; } + + /// @brief Sets verbose level + /// @param verbose Verbose level: + /// - 0: Silent mode + /// - 1: + void SetVerbose(int verbose) { fVerbose = verbose; } + + /// @brief Sets bit flag to control handler behaviour + /// @param bit Bit index + /// + /// The bit flags should be defined in an enumeration of the default class + void SetBitFlag(uint8_t bit) { fOptionBits.set(bit); } + + /// @brief Writes objects to file + void Write(); + + protected: + int fObjectID = -1; ///< Index of object + int fFileID = -1; ///< Index of file + int fDatasetID = -1; ///< Index of dataset + int fVerbose = 0; ///< Verbosity level + TDirectory* fpOutDir = nullptr; ///< Pointer to directory + std::shared_ptr<ObjectDB> fpObjDB = nullptr; ///< Pointer to object database + std::shared_ptr<CbmQaCanvas> fpCanvas = nullptr; ///< Comparison canvas + std::bitset<sizeof(CmpResult_t) * 8> fOptionBits; ///< Bitset for option + + std::string fsObjType = ""; ///< Base type of the object to be handled + std::string fsBaseName = ""; ///< Base names of the object + std::vector<TNamed*> fvpObjects; ///< Vector of objects + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerObjectHandler_h \ No newline at end of file diff --git a/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx b/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..db63fb788ef8714f9ff048cbd60dba7091e3983e --- /dev/null +++ b/core/qa/checker/CbmQaCheckerProfile1DHandler.cxx @@ -0,0 +1,134 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerProfile1DHandler.h +/// @brief Handler class for 1D-profiles (implementation) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 19.02.2023 + +#include "CbmQaCheckerProfile1DHandler.h" + +#include "CbmQaCanvas.h" +#include "CbmQaCheckerHist1DHandler.h" +#include "CbmQaCheckerTypedefs.h" + +#include "Logger.h" + +#include "TCanvas.h" +#include "TError.h" +#include "TFolder.h" +#include "TGraphAsymmErrors.h" +#include "TLegend.h" +#include "TMath.h" +#include "TMultiGraph.h" +#include "TObject.h" +#include "TProfile.h" +#include "TRatioPlot.h" +#include "TStyle.h" + +#include <limits> + +using cbm::qa::checker::Profile1DHandler; + +// --------------------------------------------------------------------------------------------------------------------- +// +Profile1DHandler::Profile1DHandler(int iObject, int iFile, int iDataset) : Hist1DHandler(iObject, iFile, iDataset) {} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Profile1DHandler::CreateCanvases() +{ + int nVersions = fpObjDB->GetNofVersions(); + int iDef = fpObjDB->GetDefaultID(); + + // ----- Canvas: comparison of original histograms, ratios and subtractions + std::string sCanvName = fsBaseName + "_cmp_canvas"; + std::string sCanvTitle = "Comparison result for \"" + fsBaseName + "\""; + fpCanvas = std::make_shared<CbmQaCanvas>(sCanvName.data(), sCanvTitle.data(), 1500, 500); + fpCanvas->cd(); + auto* pPadOrig = new TPad("p1", "p1", 0.0, 0.0, 0.3333, 1.0); + pPadOrig->SetMargin(0.20, 0.05, 0.20, 0.10); + fpCanvas->cd(); + auto* pPadRatio = new TPad("p2", "p2", 0.3333, 0.0, 0.6666, 1.0); + pPadRatio->SetMargin(0.20, 0.05, 0.20, 0.10); + fpCanvas->cd(); + auto* pPadDiff = new TPad("p3", "p3", 0.6666, 0.0, 1.0, 1.0); + pPadDiff->SetMargin(0.20, 0.05, 0.20, 0.10); + + const char* title = fvpObjects[0]->GetTitle(); + const char* titleRatio = Form("Ratio to %s", fpObjDB->GetVersionLabel(iDef).data()); + const char* titleDiff = Form("Difference from %s", fpObjDB->GetVersionLabel(iDef).data()); + const char* xAxisTitle = static_cast<TProfile*>(fvpObjects[0])->GetXaxis()->GetTitle(); + const char* yAxisTitle = static_cast<TProfile*>(fvpObjects[0])->GetYaxis()->GetTitle(); + + // Original histograms + pPadOrig->cd(); + auto* pMultiGraphOrig = new TMultiGraph(fsBaseName.data(), title); + //auto* pLegend = new TLegend(kLegendSize[0], kLegendSize[1]); + for (int iV = 0; iV < nVersions; ++iV) { + auto* pCopy = new TGraphAsymmErrors((TH1*) fvpObjects[iV]); + pCopy->SetMarkerStyle(20); + pCopy->SetTitle(fpObjDB->GetVersionLabel(iV).data()); + pMultiGraphOrig->Add(pCopy, "P"); + } + pMultiGraphOrig->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphOrig->GetYaxis()->SetTitle(yAxisTitle); + pMultiGraphOrig->Draw("A pmc plc"); + pPadOrig->BuildLegend(); + //pLegend->Draw(); + + auto* pDefault = static_cast<TProfile*>(fvpObjects[iDef])->ProjectionX(); + + // Histogram ratios to default + pPadRatio->cd(); + auto* pMultiGraphRatio = new TMultiGraph((fsBaseName + "_ratio").data(), titleRatio); + for (int iV = 0; iV < nVersions; ++iV) { + if (iV == iDef) { continue; } + auto* pRatio = static_cast<TProfile*>(fvpObjects[iV])->ProjectionX(); + auto currErrorLevel = gErrorIgnoreLevel; + gErrorIgnoreLevel = kError; + auto* pCopy = new TGraphAsymmErrors(pRatio, pDefault, "pois"); + gErrorIgnoreLevel = currErrorLevel; + pCopy->SetMarkerStyle(20); + pMultiGraphRatio->Add(pCopy, "P"); + + if (pRatio) { + delete pRatio; + pRatio = nullptr; + } + } + pMultiGraphRatio->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphRatio->GetYaxis()->SetTitle("ratio"); + pMultiGraphRatio->Draw("A pmc plc"); + + // Histogram ratios to default + pPadDiff->cd(); + auto* pMultiGraphDiff = new TMultiGraph((fsBaseName + "_diff").data(), titleDiff); + for (int iV = 0; iV < nVersions; ++iV) { + if (iV == iDef) { continue; } + auto* pDiff = static_cast<TProfile*>(fvpObjects[iV])->ProjectionX(); + pDiff->Add(pDefault, -1.); + auto* pCopy = new TGraphAsymmErrors(pDiff); + pCopy->GetYaxis()->SetTitle("difference"); + pCopy->SetMarkerStyle(20); + pMultiGraphDiff->Add(pCopy, "P"); + + if (pDiff) { + delete pDiff; + pDiff = nullptr; + } + } + pMultiGraphDiff->GetXaxis()->SetTitle(xAxisTitle); + pMultiGraphDiff->GetYaxis()->SetTitle("difference"); + pMultiGraphDiff->Draw("A pmc plc"); + fpCanvas->cd(); + pPadOrig->Draw(); + pPadRatio->Draw(); + pPadDiff->Draw(); + + if (pDefault) { + delete pDefault; + pDefault = nullptr; + } +} diff --git a/core/qa/checker/CbmQaCheckerProfile1DHandler.h b/core/qa/checker/CbmQaCheckerProfile1DHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..c2ebbc509102003a96cb549c4acb08930b7eea64 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerProfile1DHandler.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerProfile1DHandler.h +/// @brief Handler class for 1D-profiles (declaration) +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 19.02.2023 + +#ifndef CbmQaCheckerProfile1DHandler_h +#define CbmQaCheckerProfile1DHandler_h 1 + +#include "CbmQaCheckerHist1DHandler.h" + +namespace cbm::qa::checker +{ + /// @brief Specification of the handler for TProfile class + /// + class Profile1DHandler : public Hist1DHandler { + public: + /// @brief Constructor + /// @param iObject Index of object + /// @param iFile Index of file + /// @param iDataset Index of dataset + Profile1DHandler(int iObject, int iFile, int iDataset); + + /// @brief Destructor + ~Profile1DHandler() = default; + + /// @brief Creates object comparison canvas + void CreateCanvases(); + }; +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerProfile1DHandler_h diff --git a/core/qa/checker/CbmQaCheckerTypedefs.h b/core/qa/checker/CbmQaCheckerTypedefs.h new file mode 100644 index 0000000000000000000000000000000000000000..a74da874ca66f511b1ab9019a3f9ffce58aae4a9 --- /dev/null +++ b/core/qa/checker/CbmQaCheckerTypedefs.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file CbmQaCheckerTypedefs.h +/// @brief Common definitions for QA-Checker framework +/// @author S. Zharko <s.zharko@gsi.de> +/// @since 08.02.2023 + +#ifndef CbmQaCheckerTypedefs_h +#define CbmQaCheckerTypedefs_h 1 + +#include <boost/range/iterator_range.hpp> + +#include <bitset> +#include <string> +#include <unordered_map> +#include <vector> + +namespace cbm::qa::checker +{ + /// @brief Enumerations for QA-Checker execution control + /// + enum class EFlagBit + { + kSKIP_LOST_OBJECTS, //< Skips objects, which are defined in config, but not presented in file + kEND + }; + + // ----- Aliases + using MapStrToStr_t = std::unordered_map<std::string, std::string>; + using MapStrToStrVect_t = std::unordered_map<std::string, std::vector<std::string>>; + using FlagBitSet_t = std::bitset<static_cast<int>(EFlagBit::kEND)>; + using CmpResult_t = uint16_t; ///< Bitset to keep the comparison result + + template<class T> + using VectRange_t = boost::iterator_range<typename std::vector<T>::iterator>; + + // ----- Constants + constexpr double kLegendSize[2] = {.3, .05}; // width and height in % of the pad size + +} // namespace cbm::qa::checker + +#endif // CbmQaCheckerTypedefs_h \ No newline at end of file diff --git a/macro/qa/objects.yaml b/macro/qa/objects.yaml new file mode 100644 index 0000000000000000000000000000000000000000..90aa199fe5d66395bedb8a15e88446c057047d9d --- /dev/null +++ b/macro/qa/objects.yaml @@ -0,0 +1,19 @@ +# List of objects from QA output to be checked + +checker: + # Format of the input file. In the format the next indicators can be used: + # - %v : Path to directory for this version + # - %f : File type + # - %d : Dataset name + inputformat: "%v/%f_%d.root" + # Datasets (at the moment all the datasets should contain the same sets of files and objects to compare) + datasets: + - auau.10gev.mbias.mu.eb.100ev.reco + - auau.10gev.mbias.el.eb.100ev.reco + # Section defines a list of files and stored objects to be compared. If object list is empty, + # all of the histograms will be compared + files: + - name: L1_histo + objects: + - L1/p_eff_all_vs_mom + - L1/h_ghost_Rmom diff --git a/macro/qa/qa_compare.C b/macro/qa/qa_compare.C new file mode 100644 index 0000000000000000000000000000000000000000..434681ada84c1240025e9da028971d9cbd6d3162 --- /dev/null +++ b/macro/qa/qa_compare.C @@ -0,0 +1,38 @@ +/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// @file qa_compare.C +/// @author Sergei Zharko <s.zharko@gsi.de> +/// @since 06.02.2023 +/// @brief ROOT macro to run QA-Checker framework + +/// @brief Function to compare QA results +/// @param configName Name of configuration file with object list, datasets and versions +/// @param inputRootDir Root directory with input data +/// @param outputName Name of the output (default is "QaCheckerResult.root") +/// @return Result flag: +/// - 0: all objects are the same within defined comparison procedure +/// - 1: some of the objects differ within versions +int qa_compare(const char* configName, const char* inputRootDir = ".", const char* outputName = "QaCheckerResult.root") +{ + // ----- Logger settings + FairLogger::GetLogger()->SetLogScreenLevel("INFO"); + FairLogger::GetLogger()->SetColoredLog(true); + + // ----- Style settings + gStyle->SetPalette(kSolar); + + // ----- Configure QA-Checker + auto pQaChecker = std::make_unique<cbm::qa::checker::Core>(); + pQaChecker->RegisterOutFile(outputName); // Set name of the output file + pQaChecker->SetFromYAML(configName); // Read file-object map + pQaChecker->SetInputRootPath(inputRootDir); + + // ----- Run comparision routine + pQaChecker->Process("P"); + + // ----- Scan results + bool res = pQaChecker->Scan(); + return !res; +}