From ddac3c05fadc204fb699907265339c9560e65c0a Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 6 Mar 2025 22:21:58 +0100
Subject: [PATCH] online:  - QA for V0-trigger  - Masking channels (BMON as
 example) in digi event-selector

---
 algo/CMakeLists.txt                         |  4 ++
 algo/base/Definitions.h                     |  4 +-
 algo/evbuild/EventbuildChain.h              |  5 ++
 algo/evselector/DigiEventSelector.cxx       | 25 ++++++++
 algo/evselector/DigiEventSelector.h         |  9 +++
 algo/evselector/DigiEventSelectorConfig.cxx | 14 ++++-
 algo/evselector/DigiEventSelectorConfig.h   |  9 ++-
 algo/global/Reco.cxx                        |  6 ++
 algo/qa/QaTaskHeader.h                      |  2 +-
 algo/qa/trigger/V0TriggerQa.cxx             | 34 +++++++++++
 algo/qa/trigger/V0TriggerQa.h               | 68 +++++++++++++++++++++
 algo/trigger/V0Trigger.cxx                  | 10 +++
 algo/trigger/V0Trigger.h                    | 11 ++++
 13 files changed, 194 insertions(+), 7 deletions(-)
 create mode 100644 algo/qa/trigger/V0TriggerQa.cxx
 create mode 100644 algo/qa/trigger/V0TriggerQa.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 82f767e3bf..c589483d0b 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -159,6 +159,7 @@ set(SRCS
   qa/QaManager.cxx
   qa/hitfind/BmonHitfindQa.cxx
   qa/hitfind/BmonHitfindQaParameters.cxx
+  qa/trigger/V0TriggerQa.cxx
   qa/unpack/StsDigiQa.cxx
   ca/TrackingSetup.cxx
   ca/TrackingChain.cxx
@@ -200,6 +201,7 @@ target_include_directories(Algo
          ${CMAKE_CURRENT_SOURCE_DIR}/qa
          ${CMAKE_CURRENT_SOURCE_DIR}/qa/unpack
          ${CMAKE_CURRENT_SOURCE_DIR}/qa/hitfind
+         ${CMAKE_CURRENT_SOURCE_DIR}/qa/trigger
          ${CMAKE_CURRENT_SOURCE_DIR}/kf
          ${CMAKE_CURRENT_SOURCE_DIR}/kf/core
          ${CMAKE_CURRENT_SOURCE_DIR}/kf/core/utils
@@ -275,6 +277,8 @@ if (NOT CBM_ONLINE_STANDALONE)
            ${CMAKE_CURRENT_SOURCE_DIR}/detectors
            ${CMAKE_CURRENT_SOURCE_DIR}/qa
            ${CMAKE_CURRENT_SOURCE_DIR}/qa/unpack
+           ${CMAKE_CURRENT_SOURCE_DIR}/qa/hitfind
+           ${CMAKE_CURRENT_SOURCE_DIR}/qa/trigger
            ${CMAKE_CURRENT_SOURCE_DIR}/kf
            ${CMAKE_CURRENT_SOURCE_DIR}/kf/core
            ${CMAKE_CURRENT_SOURCE_DIR}/kf/core/utils
diff --git a/algo/base/Definitions.h b/algo/base/Definitions.h
index b1ff4c9ccc..c9cac7a917 100644
--- a/algo/base/Definitions.h
+++ b/algo/base/Definitions.h
@@ -79,6 +79,7 @@ namespace cbm::algo
     RecoFsd,
     Tracking,
     V0Finder,
+    V0Trigger,
   };
 
 }  // namespace cbm::algo
@@ -150,7 +151,8 @@ CBM_ENUM_DICT(cbm::algo::QaStep,
   {"RecoTof", cbm::algo::QaStep::RecoTof},
   {"RecoFsd", cbm::algo::QaStep::RecoFsd},
   {"Tracking", cbm::algo::QaStep::Tracking},
-  {"V0Finder", cbm::algo::QaStep::V0Finder}
+  {"V0Finder", cbm::algo::QaStep::V0Finder},
+  {"V0Trigger", cbm::algo::QaStep::V0Trigger}
 );
 
 #endif
diff --git a/algo/evbuild/EventbuildChain.h b/algo/evbuild/EventbuildChain.h
index ed6be01c66..32ae1e649c 100644
--- a/algo/evbuild/EventbuildChain.h
+++ b/algo/evbuild/EventbuildChain.h
@@ -64,6 +64,11 @@ namespace cbm::algo::evbuild
     /** @brief Registers tracking setup **/
     void RegisterTrackingSetup(std::shared_ptr<TrackingSetup> pSetup) { fSelector.RegisterTrackingSetup(pSetup); }
 
+    /** @brief Sets V0 trigger QA
+     ** @param pQa  Qa module
+     **/
+    void SetV0TriggerQa(std::shared_ptr<V0TriggerQa> pQa) { fV0Trigger.SetQa(pQa); }
+
    private:                                              // members
     Config fConfig;                                      ///< Global configuration
     ECbmModuleId fTriggerDet = ECbmModuleId::kNotExist;  ///< Trigger detector
diff --git a/algo/evselector/DigiEventSelector.cxx b/algo/evselector/DigiEventSelector.cxx
index bae8b3e2cf..165bbca2bc 100644
--- a/algo/evselector/DigiEventSelector.cxx
+++ b/algo/evselector/DigiEventSelector.cxx
@@ -38,12 +38,16 @@ namespace cbm::algo::evbuild
         case ECbmModuleId::kTof:
           if (!CheckTofLayers(event.fTof, entry.second)) return false;
           break;
+        case ECbmModuleId::kBmon:
+          if (!CheckBmonDiamonds(event.fBmon, entry.second)) return false;
+          break;
         default:
           throw std::runtime_error("Number of layers for " + ::ToString(entry.first) + " is not implemented");
           break;
       }
     }
 
+
     return true;
   }
   // --------------------------------------------------------------------------
@@ -127,6 +131,27 @@ namespace cbm::algo::evbuild
   // --------------------------------------------------------------------------
 
 
+  // -----   Check number of digis in selected BMON diamonds ------------------
+  bool DigiEventSelector::CheckBmonDiamonds(gsl::span<const CbmBmonDigi> digis, size_t minNum) const
+  {
+    auto itMaskedChan = fConfig.fMaskedChannels.find(ECbmModuleId::kBmon);
+    if (itMaskedChan == fConfig.fMaskedChannels.end() || itMaskedChan->second.size() == 0) {
+      return digis.size() >= minNum;
+    }
+
+    size_t nAcceptedDigis{0};
+    const auto& maskedChannels = itMaskedChan->second;
+    for (const auto& digi : digis) {
+      if (maskedChannels.find(digi.GetAddress()) == maskedChannels.end()) {
+        ++nAcceptedDigis;
+      }
+    }
+
+    return nAcceptedDigis >= minNum;
+  }
+  // --------------------------------------------------------------------------
+
+
   // -----   Info to string   -------------------------------------------------
   std::string DigiEventSelector::ToString() const
   {
diff --git a/algo/evselector/DigiEventSelector.h b/algo/evselector/DigiEventSelector.h
index a8ba4d7a95..b298875b06 100644
--- a/algo/evselector/DigiEventSelector.h
+++ b/algo/evselector/DigiEventSelector.h
@@ -74,6 +74,15 @@ namespace cbm::algo::evbuild
     bool CheckTofLayers(gsl::span<const CbmTofDigi> digis, size_t minNum) const;
 
 
+    // FIXME: apply for all detectors
+    /** @brief Test for digis in selected (=not masked) BMON diamonds
+     ** @param digis Vector of BMON digis
+     ** @param minNum Requested minimum of not masked BMON digis
+     ** @return True if the number of not masked BMON digis is above the threshold
+     **/
+    bool CheckBmonDiamonds(gsl::span<const CbmBmonDigi> digis, size_t minNum) const;
+
+
    private:                           // members
     DigiEventSelectorConfig fConfig;  ///< Configuration / parameters
     std::shared_ptr<TrackingSetup> fpTrackingSetup = nullptr;  ///< Tracking setup (access to stations info)
diff --git a/algo/evselector/DigiEventSelectorConfig.cxx b/algo/evselector/DigiEventSelectorConfig.cxx
index 647f7c0dfb..7c536e3a89 100644
--- a/algo/evselector/DigiEventSelectorConfig.cxx
+++ b/algo/evselector/DigiEventSelectorConfig.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Shreya Roy. Pierre-Alain Loizeau, Volker Friese [committer], Dominik Smith */
+   Authors: Shreya Roy. Pierre-Alain Loizeau, Volker Friese [committer], Dominik Smith, Sergei Zharko */
 
 #include "DigiEventSelectorConfig.h"
 
@@ -34,6 +34,15 @@ namespace cbm::algo::evbuild
           L_(warning) << "DigiEventSelectorConfig: Ignoring minimum 0 for layers in " << ::ToString(det);
       }
     }
+    if (auto maskedChannels = config["maskedChannels"]) {
+      for (YAML::const_iterator it = maskedChannels.begin(); it != maskedChannels.end(); ++it) {
+        auto det   = ToCbmModuleIdCaseInsensitive(it->first.as<std::string>());
+        auto value = it->second.as<std::vector<uint32_t>>();
+        if (value.size() > 0) {
+          fMaskedChannels[det] = std::unordered_set<uint32_t>(value.begin(), value.end());
+        }
+      }
+    }
   }
   // --------------------------------------------------------------------------
 
@@ -50,6 +59,7 @@ namespace cbm::algo::evbuild
       auto det                 = ToString(entry.first);
       result["minLayers"][det] = entry.second;
     }
+    // FIXME: implement masked channels storage
     return result;
   }
   // --------------------------------------------------------------------------
diff --git a/algo/evselector/DigiEventSelectorConfig.h b/algo/evselector/DigiEventSelectorConfig.h
index 43276c739d..fa97bdcc13 100644
--- a/algo/evselector/DigiEventSelectorConfig.h
+++ b/algo/evselector/DigiEventSelectorConfig.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Shreya Roy. Pierre-Alain Loizeau, Volker Friese [committer], Dominik Smith */
+   Authors: Shreya Roy. Pierre-Alain Loizeau, Volker Friese [committer], Dominik Smith, Sergei Zharko */
 
 #ifndef CBM_ALGO_EVBUILD_DIGIEVENTSELECTORCONFIG_H
 #define CBM_ALGO_EVBUILD_DIGIEVENTSELECTORCONFIG_H 1
@@ -9,6 +9,7 @@
 
 #include <cstdint>
 #include <map>
+#include <unordered_set>
 
 #include <yaml-cpp/yaml.h>
 
@@ -45,10 +46,12 @@ namespace cbm::algo::evbuild
     /** @brief Save to YAML **/
     YAML::Node ToYaml() const;
 
-
    private:
     std::map<ECbmModuleId, size_t> fMinNumDigis;   ///< Key: detector, value: minimal number of digis
     std::map<ECbmModuleId, size_t> fMinNumLayers;  ///< Key: detector, value: Minimal number of layers
+
+    /** @brief A map of masked digi addresses, which should not participate in the event building **/
+    std::map<ECbmModuleId, std::unordered_set<uint32_t>> fMaskedChannels;
   };
 
 
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 0f192df1b6..8f042b149d 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -192,6 +192,12 @@ void Reco::Init(const Options& opts)
     evbuild::Config config(YAML::LoadFile(configFile.string()));
     fEventBuild =
       std::make_unique<evbuild::EventbuildChain>(config, (Opts().Has(QaStep::EventBuilding) ? fSender : nullptr));
+    if (fQaManager != nullptr && Opts().Has(QaStep::V0Trigger)) {
+      // FIXME: Replace with a common function SetTriggerQa(fQaManager)
+      auto pTriggerQa = std::make_shared<evbuild::V0TriggerQa>(fQaManager);
+      pTriggerQa->Init();
+      fEventBuild->SetV0TriggerQa(pTriggerQa);
+    }
     fEventBuild->RegisterTrackingSetup(pTrackingSetup);
   }
 
diff --git a/algo/qa/QaTaskHeader.h b/algo/qa/QaTaskHeader.h
index ceb03e5ad4..e910a6a103 100644
--- a/algo/qa/QaTaskHeader.h
+++ b/algo/qa/QaTaskHeader.h
@@ -51,7 +51,7 @@ namespace cbm::algo::qa
     ///
     /// The task can be inactive, if a nullptr qa::Manager was passed to the constructor. If it is the case,
     /// the fpData instance is not defined, and no actions on the task should be performed
-    bool IsActive() const { return static_cast<bool>(fpData != nullptr); }
+    bool IsActive() const { return fpData.get(); }
 
     /// \brief Gets name of the task
     const std::string& GetTaskName() { return fsName; }
diff --git a/algo/qa/trigger/V0TriggerQa.cxx b/algo/qa/trigger/V0TriggerQa.cxx
new file mode 100644
index 0000000000..234cf13b19
--- /dev/null
+++ b/algo/qa/trigger/V0TriggerQa.cxx
@@ -0,0 +1,34 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   V0TriggerQa.cxx
+/// \brief  A V0-trigger QA (implementation)
+/// \since  06.03.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "qa/trigger/V0TriggerQa.h"
+
+#include "qa/Histogram.h"
+
+using cbm::algo::evbuild::V0TriggerQa;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0TriggerQa::Init()
+{
+  // Histograms
+  fphPairDeltaT  = MakeObj<qa::H1D>("v0trigger_pair_delta_t", "Time difference of track pair;#Delta t [ns];Counts",
+                                   kPairDeltaTB, kPairDeltaTL, kPairDeltaTU);
+  fphPairZVertex = MakeObj<qa::H1D>("v0trigger_pair_z_vertex", "z-verex of track pair;z [cm];Counts", kPairZVertexB,
+                                    kPairZVertexL, kPairZVertexU);
+  fphPairDca     = MakeObj<qa::H1D>("v0trigger_pair_dca", "Track pair distance of closest approach;DCA [cm];Counts",
+                                kPairDcaB, kPairDcaL, kPairDcaU);
+
+  // Canvas
+  auto canv = qa::CanvasConfig(GetTaskName(), "V0 Trigger summary", 3, 1);
+  canv.AddPadConfig(qa::PadConfig(fphPairDeltaT, "hist"));
+  canv.AddPadConfig(qa::PadConfig(fphPairZVertex, "hist"));
+  canv.AddPadConfig(qa::PadConfig(fphPairDca, "hist"));
+  AddCanvasConfig(canv);
+}
diff --git a/algo/qa/trigger/V0TriggerQa.h b/algo/qa/trigger/V0TriggerQa.h
new file mode 100644
index 0000000000..6f1aa13dc6
--- /dev/null
+++ b/algo/qa/trigger/V0TriggerQa.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   V0TriggerQa.h
+/// \brief  A V0-trigger QA
+/// \since  06.03.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "qa/QaTaskHeader.h"
+
+namespace cbm::algo::qa
+{
+  class H1D;
+}  // namespace cbm::algo::qa
+
+
+namespace cbm::algo::evbuild
+{
+  /// \class V0TriggerQa
+  /// \brief A QA module for the V0-trigger
+  class V0TriggerQa : public qa::TaskHeader {
+   public:
+    friend class V0Trigger;  // for access to histograms
+
+    /// \brief Constructor
+    /// \param pManager  Pointer to the QA manager
+    /// \param name      Name of the QA (directory)
+    V0TriggerQa(const std::unique_ptr<qa::Manager>& pManager) : qa::TaskHeader(pManager, "V0Trigger") {}
+
+    /// \brief Copy constructor
+    V0TriggerQa(const V0TriggerQa&) = delete;
+
+    /// \brief Move constructor
+    V0TriggerQa(V0TriggerQa&&) = delete;
+
+    /// \brief Destructor
+    ~V0TriggerQa() = default;
+
+    /// \brief Copy assignment operator
+    V0TriggerQa& operator=(const V0TriggerQa&) = delete;
+
+    /// \brief Move assignment operator
+    V0TriggerQa& operator=(V0TriggerQa&&) = delete;
+
+    /// \brief Initializes the task
+    void Init();
+
+   private:
+    //* Constants
+    static constexpr int kPairDeltaTB{100};       ///< Track pair time difference: n bins
+    static constexpr double kPairDeltaTL{-50.};   ///< Track pair time difference: lower bound [ns]
+    static constexpr double kPairDeltaTU{+50.};   ///< Track pair time difference: upper bound [ns]
+    static constexpr int kPairZVertexB{120};      ///< Track pair z vertex: n bins
+    static constexpr double kPairZVertexL{-60.};  ///< Track pair z vertex: lower bound [cm]
+    static constexpr double kPairZVertexU{+60.};  ///< Track pair z vertex: upper bound [cm]
+    static constexpr int kPairDcaB{100};          ///< Track pair DCA: n bins
+    static constexpr double kPairDcaL{-2.};       ///< Track pair DCA: lower bound [cm]
+    static constexpr double kPairDcaU{+2.};       ///< Track pair DCA: upper bound [cm]
+
+    //* Histograms
+    qa::H1D* fphPairDeltaT{nullptr};   ///< Track pair delta T
+    qa::H1D* fphPairZVertex{nullptr};  ///< Track pair z-vertex
+    qa::H1D* fphPairDca{nullptr};      ///< Track pair distance at closest approach
+  };
+}  // namespace cbm::algo::evbuild
diff --git a/algo/trigger/V0Trigger.cxx b/algo/trigger/V0Trigger.cxx
index bf9264801a..49cd418258 100644
--- a/algo/trigger/V0Trigger.cxx
+++ b/algo/trigger/V0Trigger.cxx
@@ -32,6 +32,11 @@ namespace cbm::algo::evbuild
         // Check track time difference
         float time1 = trackIter1->fParPV.GetTime();
         float time2 = trackIter2->fParPV.GetTime();
+
+        if (fpQa->IsActive()) {
+          fpQa->fphPairDeltaT->Fill(time1 - time2);
+        }
+
         if (time2 < time1) {
           result.second.errTracksUnsorted++;
           continue;
@@ -42,6 +47,11 @@ namespace cbm::algo::evbuild
 
         // Check PCA cuts
         auto [zVertex, dist] = CalcPCA(trackIter1->fParPV, trackIter2->fParPV);
+        if (fpQa->IsActive()) {
+          fpQa->fphPairZVertex->Fill(zVertex);
+          fpQa->fphPairDca->Fill(dist);
+        }
+
         if (dist < config.PairDist_max()) {
           result.second.numTrackPairsAfterDistCut++;
           if (zVertex >= config.PairZ_min() && zVertex <= config.PairZ_max()) {
diff --git a/algo/trigger/V0Trigger.h b/algo/trigger/V0Trigger.h
index 6bed8ce5d2..3736b64f23 100644
--- a/algo/trigger/V0Trigger.h
+++ b/algo/trigger/V0Trigger.h
@@ -7,6 +7,7 @@
 #include "CaTrack.h"
 #include "CaVector.h"
 #include "V0TriggerConfig.h"
+#include "qa/trigger/V0TriggerQa.h"
 
 #include <utility>
 #include <vector>
@@ -50,6 +51,7 @@ namespace cbm::algo::evbuild
     /** @brief Constructor **/
     V0Trigger() = default;
 
+
     /** @brief Execution
      ** @param  tracks      Input track vector
      ** @param  config      Trigger configuration
@@ -57,6 +59,13 @@ namespace cbm::algo::evbuild
      **/
     Result operator()(const TrackVector& tracks, const V0TriggerConfig& config) const;
 
+
+    /** @brief Sets QA module
+     ** @param pQa  Pointer to the QA module
+     **/
+    void SetQa(std::shared_ptr<V0TriggerQa> pQa) { fpQa = pQa; }
+
+
     /** @brief Info to string **/
     std::string ToString() const;
 
@@ -84,6 +93,8 @@ namespace cbm::algo::evbuild
      ** @return decision
      **/
     bool IsPrimary(const TrackParam& track, const V0TriggerConfig& config) const;
+
+    std::shared_ptr<V0TriggerQa> fpQa{std::make_shared<V0TriggerQa>(nullptr)};  //! QA module
   };
 
 }  // namespace cbm::algo::evbuild
-- 
GitLab