From 6e91016a5bd50ad6250d40782fc1072219c7a39e Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Wed, 5 Feb 2025 20:03:41 +0100
Subject: [PATCH 01/17] online: BMON digi calibrator

---
 algo/detectors/bmon/Calibrate.cxx | 7 -------
 core/data/tof/CbmTofAddress.cxx   | 1 +
 external/InstallParameter.cmake   | 2 ++
 3 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx
index d55198549..fc2a17f29 100644
--- a/algo/detectors/bmon/Calibrate.cxx
+++ b/algo/detectors/bmon/Calibrate.cxx
@@ -57,13 +57,6 @@ Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSelectionBitsOffset
   }
   fChannelDeadTime = std::vector<double>(fChannelOffset.back(), std::numeric_limits<double>::quiet_NaN());
 
-  // **** DEBUG: BEGIN
-  for (size_t iO = 0; iO < fChannelOffset.size(); ++iO) {
-    L_(info) << "Channel offset: diamond=" << iO << ", offset=" << fChannelOffset[iO];
-  }
-  L_(info) << "Selection mask: 0b" << std::bitset<32>(fSetup.selectionMask);
-  L_(info) << "Bits offset: " << static_cast<uint32_t>(fSelectionBitsOffset);
-  L_(info) << "Size of the channel dead time vector: " << fChannelDeadTime.size();
   // **** DEBUG: END
 }
 
diff --git a/core/data/tof/CbmTofAddress.cxx b/core/data/tof/CbmTofAddress.cxx
index 29c157345..71554dd1a 100644
--- a/core/data/tof/CbmTofAddress.cxx
+++ b/core/data/tof/CbmTofAddress.cxx
@@ -66,3 +66,4 @@ std::string CbmTofAddress::ToString(int32_t address)
   msg << ", RpcType=" << setw(2) << CbmTofAddress::GetRpcType(address);
   return msg.str();
 }
+
diff --git a/external/InstallParameter.cmake b/external/InstallParameter.cmake
index 77e3f424d..7d02e04ad 100644
--- a/external/InstallParameter.cmake
+++ b/external/InstallParameter.cmake
@@ -1,5 +1,7 @@
 set(PARAMETER_VERSION cf6a880a56a8695b485efe358231270e9179cb82) # 2025/02/15
 set(PARAMETER_SRC_URL "https://git.cbm.gsi.de/CbmSoft/cbmroot_parameter.git")
+#set(PARAMETER_VERSION 96b2189ac86b3d49c32f63da76e68f66544bba55) # 2025/05/02, BMON hitfinder parameters for online
+#set(PARAMETER_SRC_URL "https://git.cbm.gsi.de/s.zharko/cbmroot_parameter.git")
 
 download_project_if_needed(PROJECT         Parameter_source
                            GIT_REPOSITORY  ${PARAMETER_SRC_URL}
-- 
GitLab


From e4cbd0fafa90eb6396f1d4b5e618d68fe29c7982 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Sun, 9 Feb 2025 15:48:30 +0100
Subject: [PATCH 02/17] online: BMON hitfinder

---
 algo/global/Reco.cxx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 8460ea28b..087fbe575 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -396,6 +396,8 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
     // ***** DEBUG: BEGIN
     if constexpr (0) {
       int nEvents = events.size();
+      size_t nBmonHitsOneChannel{0};
+      size_t nBmonHitsTwoChannels{0};
       for (int iE = 0; iE < nEvents; ++iE) {
         const auto& event = events[iE];
         // Calibrate TOF digis:
@@ -408,6 +410,8 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
           fBmonHitFinderQa->Exec();
         }
       }
+      L_(info) << "!!!! BMON hits with two channels: " << nBmonHitsTwoChannels << " / "
+               << (nBmonHitsTwoChannels + nBmonHitsOneChannel);
     }
     // ***** DEBUG: END
 
-- 
GitLab


From 745b61828fad09ba78d936cd4a8f98e25ba824f6 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 30 Jan 2025 01:18:07 +0100
Subject: [PATCH 03/17] - ca: misalignment tolerances of x and y are set to 0 -
 V0Finder:   - YAML config   - cuts on velocity of pion and proton candidates 
  - enhanced cut for decay length (0.1 -> 5 cm)

---
 algo/evselector/KfpfV0FinderConfig.h | 74 ++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 algo/evselector/KfpfV0FinderConfig.h

diff --git a/algo/evselector/KfpfV0FinderConfig.h b/algo/evselector/KfpfV0FinderConfig.h
new file mode 100644
index 000000000..cbeaec0a7
--- /dev/null
+++ b/algo/evselector/KfpfV0FinderConfig.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpfV0FinderConfig.h
+/// \date   29.01.2025
+/// \brief  Configuration structure for V0 selector in mCBM
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "yaml/Yaml.h"
+
+#include <vector>
+
+namespace cbm::algo::kfpf
+{
+  /// \struct CutsKfpf
+  /// \brief  Cuts for the KFParticleFinder
+  struct CutsKfpf {
+    float minDecayLength;  ///< Minimal decay length of particles [cm]
+    float minDecayLDL;     ///< Minimal value of decay length to decay length error ratio
+    float maxChi2NdfPrim;  ///< Maximal chi2/NDF for primary particles (coming from the PV)
+    float maxChi2NdfGeo;   ///< Maximal chi2/NDF for V0 candidates
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&CutsKfpf::minDecayLength, "min_decay_length", "Minimal decay length of particles [cm]"),
+      yaml::Property(&CutsKfpf::minDecayLDL, "min_decay_ldl", "Minimal value of decay length to decay length error ratio"),
+      yaml::Property(&CutsKfpf::maxChi2NdfPrim, "max_chi2_ndf_prim", "Maximal chi2/NDF for primary particles"),
+      yaml::Property(&CutsKfpf::maxChi2NdfGeo, "max_chi2_ndf_geo", "Maximal chi2/NDF for V0 candidates"));
+  };
+
+  /// \struct ParticlePid
+  /// \brief  PID and pre-selection cuts for a given particle
+  struct ParticlePid {
+    int pdg;             ///< PDG code for particle
+    double minDca;       ///< Minimal DCA to PV [cm]
+    double minVelocity;  ///< Minimal velocity [cm/ns]
+    double maxVelocity;  ///< Maximal velocity [cm/ns]
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&ParticlePid::pdg, "pdg", "PDG code of the particle"),
+      yaml::Property(&ParticlePid::minDca, "min_dca", "Minimal DCA to PV [cm]"),
+      yaml::Property(&ParticlePid::minVelocity, "min_velocity", "Minimal velocity [cm/ns]"),
+      yaml::Property(&ParticlePid::maxVelocity, "max_velocity", "Maximal velocity [cm/ns]"));
+  };
+
+  /// \struct Cuts;
+  struct Cuts {
+    CutsKfpf kfpf;                       ///< KFParticleFinder specific cuts
+    std::vector<ParticlePid> particles;  ///< Daughter PID cuts and other properties
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&Cuts::kfpf, "kfpf", "Specific cuts for the KFParticleFinder"),
+      yaml::Property(&Cuts::particles,  "particles", "Particle identification cuts and properties"));
+  };
+
+  /// \struct LambdaFinderConfig
+  /// \brief  Configuration for the V0 finder
+  struct V0FinderConfig {
+    Cuts cuts;                     ///< Different selection cuts
+    double tZeroOffset;            ///< Offset for T0 [ns]
+    double qpAssignedUncertainty;  ///< Assigned relative uncertainty for q/p estimation
+    int primaryAssignedPdg;        ///< Assigned PDG hypothesis for primary particles
+    int reconstructPdg;            ///< PDG of the particle, the decay of which is to be reconstructed
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&V0FinderConfig::cuts, "cuts", "Different selection cuts"),
+      yaml::Property(&V0FinderConfig::tZeroOffset, "t0_offset", "The t0 offset [ns]"),
+      yaml::Property(&V0FinderConfig::qpAssignedUncertainty, "qa_uncertainty", "Assigned relative uncertainty for q/p"),
+      yaml::Property(&V0FinderConfig::primaryAssignedPdg, "primary_pdg", "Assigned PDG code for primary tracks"),
+      yaml::Property(&V0FinderConfig::reconstructPdg, "reconstruct_pdg", "PDG code of the particle to be reconstructed"));
+  };
+}  // namespace cbm::algo::kfpf
-- 
GitLab


From b389473c0e2684e31d9c552b840baf1120fe7856 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 27 Jan 2025 11:55:39 +0100
Subject: [PATCH 04/17] cbmreco: Hit reconstruction in digi-events

---
 algo/base/Options.cxx |  1 +
 algo/base/Options.h   |  3 ++
 algo/global/Reco.cxx  | 73 ++++++++++++++++++++++++++++++++-----------
 algo/global/Reco.h    |  5 ++-
 4 files changed, 63 insertions(+), 19 deletions(-)

diff --git a/algo/base/Options.cxx b/algo/base/Options.cxx
index ea9ecace3..abce4f27d 100644
--- a/algo/base/Options.cxx
+++ b/algo/base/Options.cxx
@@ -93,6 +93,7 @@ Options::Options(int argc, char** argv)
     ("compress-archive", po::bool_switch(&fCompressArchive)->default_value(false), "Enable compression for output archives")
     ("steps", po::value(&fRecoSteps)->multitoken()->default_value({Step::Unpack, Step::DigiTrigger, Step::LocalReco, Step::Tracking})->value_name("<steps>"),
       "space separated list of reconstruction steps (unpack, digitrigger, localreco, ...)")
+    ("event-reco", po::bool_switch(&fReconstructDigiEvents)->default_value(false), "runs digi event reconstruction (local reco, tracking, trigger)")
     ("systems,s", po::value(&fDetectors)->multitoken()->default_value({Subsystem::STS, Subsystem::TOF, Subsystem::BMON, Subsystem::MUCH, Subsystem::RICH, Subsystem::TRD, Subsystem::TRD2D})->value_name("<detectors>"),
       "space separated list of detectors to process (sts, mvd, ...)")
     ("child-id,c", po::value(&fChildId)->default_value("00")->value_name("<id>"), "online process id on node")
diff --git a/algo/base/Options.h b/algo/base/Options.h
index 5a9d232fc..f1b508a06 100644
--- a/algo/base/Options.h
+++ b/algo/base/Options.h
@@ -68,6 +68,8 @@ namespace cbm::algo
 
     bool Has(QaStep qastep) const;
 
+    bool ReconstructDigiEvents() const { return fReconstructDigiEvents; }
+
    private:                  // members
     std::string fParamsDir;  // TODO: can we make this a std::path?
     std::string fInputLocator;
@@ -97,6 +99,7 @@ namespace cbm::algo
     uint64_t fRunId        = 2391;
     uint64_t fRunStartTime = 0;
     bool fCollectAuxData   = false;
+    bool fReconstructDigiEvents = false;
   };
 
 }  // namespace cbm::algo
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 087fbe575..a990278ad 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -241,6 +241,15 @@ void Reco::Init(const Options& opts)
     fTrdHitfind  = std::make_unique<trd::Hitfind>(setup, setup2d);
   }
 
+  // Digi event reconstruction:
+  {
+    fbReconstructDigiEvents = Opts().ReconstructDigiEvents();
+    // It makes no sence to reconstruct an event, if there is no STS, TRD or TOF
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::STS);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TRD);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TOF);
+  }
+
   // Tracking
   if (Opts().Has(Step::Tracking)) {
     if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
@@ -393,27 +402,16 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       QueueEvbuildMetrics(evbuildMonitor);
     }
 
-    // ***** DEBUG: BEGIN
-    if constexpr (0) {
-      int nEvents = events.size();
-      size_t nBmonHitsOneChannel{0};
-      size_t nBmonHitsTwoChannels{0};
-      for (int iE = 0; iE < nEvents; ++iE) {
-        const auto& event = events[iE];
-        // Calibrate TOF digis:
-        auto [bmonDigis, bmonCalMonitor]         = (*fBmonCalibrator)(event.fBmon);
-        auto [bmonHits, hitmonitor, digiIndices] = (*fBmonHitFinder)(bmonDigis);
-        if (fBmonHitFinderQa != nullptr) {
-          fBmonHitFinderQa->RegisterDigis(&bmonDigis);
-          fBmonHitFinderQa->RegisterHits(&bmonHits);
-          fBmonHitFinderQa->RegisterDigiIndices(&digiIndices);
-          fBmonHitFinderQa->Exec();
+    // --- Reconstruct and select digi events
+    if (Opts().ReconstructDigiEvents()) {
+      size_t nDiscardedEvents{0};
+      for (const auto& event : events) {
+        if (!ReconstructEvent(event)) {
+          ++nDiscardedEvents;
         }
       }
-      L_(info) << "!!!! BMON hits with two channels: " << nBmonHitsTwoChannels << " / "
-               << (nBmonHitsTwoChannels + nBmonHitsOneChannel);
+      L_(info) << "Rate of discarded events " << double(nDiscardedEvents) / events.size();
     }
-    // ***** DEBUG: END
 
     // --- Filter data for output
     if (Opts().HasOutput(RecoData::DigiTimeslice)) {
@@ -491,6 +489,45 @@ void Reco::PrintTimings(xpu::timings& timings)
   }
 }
 
+bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
+{
+  RecoResults recoEvent;
+  //* STS hit reconstruction
+  {
+    auto stsResults = (*fStsHitFinder)(digiEvent.fSts);
+    if (stsResults.hits.NElements() < 2) {  // TODO: Provide a config for cuts (testing mode for now)
+      return false;
+    }
+    recoEvent.stsHits = stsResults.hits;
+  }
+
+  //* TOF hit reconstruction
+  {
+    auto [caldigis, calmonitor]          = (*fTofCalibrator)(digiEvent.fTof);
+    auto [hits, hitmonitor, digiindices] = (*fTofHitFinder)(caldigis);
+    if (hits.NElements() < 1) {  // TODO: Provide a config for cuts (testing mode for now)
+      return false;
+    }
+    recoEvent.tofHits = std::move(hits);
+  }
+
+  //* TRD hit reconstruction
+  {
+    // FIXME: additional copy of digis, figure out how to pass 1d + 2d digis at once to hitfinder
+    const auto& digis1d = digiEvent.fTrd;
+    const auto& digis2d = digiEvent.fTrd2d;
+    PODVector<CbmTrdDigi> allDigis{};
+    allDigis.reserve(digis1d.size() + digis2d.size());
+    std::copy(digis1d.begin(), digis1d.end(), std::back_inserter(allDigis));
+    std::copy(digis2d.begin(), digis2d.end(), std::back_inserter(allDigis));
+    auto trdResults   = (*fTrdHitfind)(allDigis);
+    recoEvent.trdHits = std::move(std::get<0>(trdResults));
+  }
+
+  return true;
+}
+
+
 template<class Unpacker>
 auto Reco::RunUnpacker(const std::unique_ptr<Unpacker>& unpacker, const fles::Timeslice& ts) -> UnpackResult_t<Unpacker>
 {
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 6182decda..5a2c62e63 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -130,13 +130,16 @@ namespace cbm::algo
 
     void Init(const Options&);
     RecoResults Run(const fles::Timeslice&);
+
+    bool ReconstructEvent(const DigiEvent& event);
     void Finalize();
     void PrintTimings(xpu::timings&);
 
     void QueueProcessingExtraMetrics(const ProcessingExtraMonitor&);
 
    private:
-    bool fInitialized = false;
+    bool fInitialized            = false;
+    bool fbReconstructDigiEvents = false;
     ChainContext fContext;
     xpu::timings fTimesliceTimesAcc;
     std::shared_ptr<HistogramSender> fSender;
-- 
GitLab


From c3c14ff6073ac6697be3fdaf131f3ffcea07bc29 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Tue, 28 Jan 2025 13:58:00 +0100
Subject: [PATCH 05/17] online: adding tracking into digi-event reconstruction;
 some modifications to tracking data reading

---
 algo/ca/TrackingChain.cxx                  | 57 ++++++++++++----
 algo/ca/TrackingChain.h                    |  6 +-
 algo/ca/core/data/CaDataManager.cxx        | 48 ++++++--------
 algo/ca/core/data/CaDataManager.h          | 13 ++--
 algo/ca/core/utils/CaTrackingMonitor.h     | 10 +++
 algo/evselector/RecoEventSelectorMonitor.h | 75 ++++++++++++++++++++++
 algo/global/Reco.cxx                       | 64 ++++++++++++++++--
 algo/global/Reco.h                         |  7 +-
 algo/global/RecoResults.h                  |  1 +
 9 files changed, 225 insertions(+), 56 deletions(-)
 create mode 100644 algo/evselector/RecoEventSelectorMonitor.h

diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index 1d481261a..d3ee72ab7 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -40,8 +40,9 @@ using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager, std::string_view name)
+TrackingChain::TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager, std::string_view name)
   : fQa(Qa(qaManager, name))
+  , fRecoMode(recoMode)
 {
 }
 
@@ -116,8 +117,13 @@ void TrackingChain::Init()
 
   // ------ Initialize CA framework
   fCaMonitor.Reset();
-  fCaFramework.SetNofThreads(Opts().NumOMPThreads() == std::nullopt ? openmp::GetMaxThreads()
-                                                                    : *(Opts().NumOMPThreads()));
+  if (ECbmRecoMode::Timeslice == fRecoMode) {
+    fCaFramework.SetNofThreads(Opts().NumOMPThreads() == std::nullopt ? openmp::GetMaxThreads()
+                                                                      : *(Opts().NumOMPThreads()));
+  }
+  else {
+    fCaFramework.SetNofThreads(1);
+  }
   fCaFramework.ReceiveParameters(std::move(parameters));
   fCaFramework.Init(ca::TrackingMode::kMcbm);
 
@@ -174,18 +180,26 @@ void TrackingChain::PrepareInput(Input_t recoResults)
   faHitExternalIndices.clear();
   faHitExternalIndices.reserve(nHitsTot);
   if (fbDetUsed[EDetectorID::kSts]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareStsHits);
     ReadHits<EDetectorID::kSts>(recoResults.stsHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareStsHits);
   }
   if (fbDetUsed[EDetectorID::kTrd]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareTrdHits);
     ReadHits<EDetectorID::kTrd>(recoResults.trdHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareTrdHits);
   }
   if (fbDetUsed[EDetectorID::kTof]) {
+    fCaMonitorData.StartTimer(ca::ETimer::PrepareTofHits);
     ReadHits<EDetectorID::kTof>(recoResults.tofHits);
+    fCaMonitorData.StopTimer(ca::ETimer::PrepareTofHits);
   }
   faHitExternalIndices.shrink_to_fit();
   fCaDataManager.SetNhitKeys(fNofHitKeys);
   L_(debug) << "Tracking chain: " << fCaDataManager.GetNofHits() << " hits will be passed to the ca::Framework";
+  fCaMonitorData.StartTimer(ca::ETimer::InputDataTransmission);
   fCaFramework.ReceiveInputData(fCaDataManager.TakeInputData());
+  fCaMonitorData.StopTimer(ca::ETimer::InputDataTransmission);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -223,12 +237,13 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
     trackFirstHit += nHits;
   }
 
-  L_(info) << "TrackingChain: Timeslice contains " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrack)
-           << " tracks, with " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoStsHit) << " sts hits, "
-           << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTofHit) << " tof hits, "
-           << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrdHit) << " trd hits"
-           << "; the FindTracks routine ran " << fCaMonitorData.GetTimer(ca::ETimer::FindTracks).GetTotal() << " s";
-
+  if (ECbmRecoMode::Timeslice == fRecoMode) {
+    L_(info) << "TrackingChain: Timeslice contains " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrack)
+             << " tracks, with " << fCaMonitorData.GetCounterValue(ca::ECounter::RecoStsHit) << " sts hits, "
+             << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTofHit) << " tof hits, "
+             << fCaMonitorData.GetCounterValue(ca::ECounter::RecoTrdHit) << " trd hits"
+             << "; the FindTracks routine ran " << fCaMonitorData.GetTimer(ca::ETimer::FindTracks).GetTotal() << " s";
+  }
 
   // QA
   if (fQa.IsActive()) {
@@ -327,6 +342,7 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
 
       //int iHitExt     = iOffset + iPartHit;
       // ---- Fill ca::Hit
+      fCaMonitorData.StartTimer(ca::ETimer::CaHitCreation);
       ca::Hit caHit;
       if constexpr (IsSts) {
         caHit.SetFrontKey(firstHitKey + hit.fFrontClusterId);
@@ -337,6 +353,15 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
         caHit.SetFrontKey(firstHitKey + iPartHit);
         caHit.SetBackKey(caHit.FrontKey());
       }
+
+      if constexpr (IsTof) {
+        // Cut the BMon hits (if any)
+        if (hit.Z() < 0.1) {
+          // FIXME: Provide BMon addresses explicitly in all the parameter files
+          continue;
+        }
+      }
+
       caHit.SetX(hit.X());
       caHit.SetY(hit.Y());
       caHit.SetZ(hit.Z());
@@ -366,11 +391,16 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
       caHit.SetStation(iStActive);
       caHit.SetId(fCaDataManager.GetNofHits());
       if (caHit.Check()) {
-        if ((caHit.T() < lastTime - 1000.) && (dataStream < 100000)) {
-          dataStream++;
+        if (ECbmRecoMode::Timeslice == fRecoMode) {
+          if ((caHit.T() < lastTime - 1000.) && (dataStream < 100000)) {
+            dataStream++;
+          }
+          lastTime = caHit.T();
+          fCaDataManager.PushBackHit(caHit, dataStreamDet | dataStream);
+        }
+        else {
+          fCaDataManager.PushBackHit(caHit);  // A single data stream in event-by-event mode
         }
-        lastTime = caHit.T();
-        fCaDataManager.PushBackHit(caHit, dataStreamDet | dataStream);
         faHitExternalIndices.push_back(std::make_tuple(DetID, iPartition, iPartHit));
         if (fNofHitKeys <= caHit.FrontKey()) {
           fNofHitKeys = caHit.FrontKey() + 1;
@@ -396,6 +426,7 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
           fCaMonitorData.IncrementCounter(ca::ECounter::UndefinedTofHit);
         }
       }
+      fCaMonitorData.StopTimer(ca::ETimer::CaHitCreation);
       //prevTime = caHit.T();
       // ---- Update number of hit keys
     }  // iPartHit
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 3f949d360..5d30319fb 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -41,9 +41,10 @@ namespace cbm::algo
   class TrackingChain : public SubChain {
    public:
     /// \brief Constructor from parameters
+    /// \param recoMode     Reconstruction mode
     /// \param pManager a QA-manager
     /// \param name A name of the task (histograms directory)
-    TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr, std::string_view name = "");
+    TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr, std::string_view name = "");
 
     /// \struct Input_t
     /// \brief  Input to the TrackingChain
@@ -92,6 +93,7 @@ namespace cbm::algo
     /// \brief  Provides action in the end of the run
     void Finalize();
 
+
    private:
     // *********************
     // **  Utility functions
@@ -132,6 +134,8 @@ namespace cbm::algo
     /// \note  Indexing: [globalHitID], value: (DetID, partitionID, hitID)
     ca::Vector<std::tuple<ca::EDetectorID, uint32_t, uint32_t>> faHitExternalIndices{"faHitExternalIndices"};
 
+    ECbmRecoMode fRecoMode{ECbmRecoMode::Undefined};  ///< Reconstruction mode
+
     static constexpr bool kDEBUG = false;  ///< Debug mode
   };
 
diff --git a/algo/ca/core/data/CaDataManager.cxx b/algo/ca/core/data/CaDataManager.cxx
index 7d237151d..4e74a2524 100644
--- a/algo/ca/core/data/CaDataManager.cxx
+++ b/algo/ca/core/data/CaDataManager.cxx
@@ -17,23 +17,6 @@
 using cbm::algo::ca::DataManager;
 using cbm::algo::ca::InputData;
 
-// ---------------------------------------------------------------------------------------------------------------------
-//
-bool DataManager::SendInputData(InputData& destination)
-{
-  // Set boundary hit indexes
-  InitData();
-
-  // Check data before input
-  if (CheckInputData<constants::control::InputDataQaLevel>()) {
-    destination = std::move(fInputData);
-    assert(this->GetNofHits() == 0);
-    return true;
-  }
-  LOG(error) << "L1: Attempt to set up inconsistent input data";
-  return false;
-}
-
 // ---------------------------------------------------------------------------------------------------------------------
 //
 InputData&& DataManager::TakeInputData()
@@ -93,16 +76,21 @@ void DataManager::InitData()
 {
   // set the end pointers to data streams
   // TODO: SZh 14.08.2023: Move the max allowed number of streams to the constants.h
-  if (fInputData.fvStreamStartIndices.size() > 3000) {
-    LOG(warning) << "ca::DataManager: unexpected order of input data: too many data streams!!! ";
-    fInputData.fvStreamStartIndices.shrink(3000);
-  }
+
   int nStreams = fInputData.fvStreamStartIndices.size();
-  fInputData.fvStreamStopIndices.reset(nStreams);
-  for (int i = 0; i < nStreams - 1; i++) {
-    fInputData.fvStreamStopIndices[i] = fInputData.fvStreamStartIndices[i + 1];
+  if (!nStreams) {  // No data streams provided
+    fInputData.fvStreamStartIndices.push_back(0);
+    fInputData.fvStreamStopIndices.push_back(fInputData.fvHits.size());
   }
-  if (nStreams > 0) {
+  else {
+    if (nStreams > 3000) {
+      LOG(warning) << "ca::DataManager: unexpected order of input data: too many data streams!!! ";
+      fInputData.fvStreamStartIndices.shrink(3000);
+    }
+    fInputData.fvStreamStopIndices.reset(nStreams);
+    for (int i = 0; i < nStreams - 1; i++) {
+      fInputData.fvStreamStopIndices[i] = fInputData.fvStreamStartIndices[i + 1];
+    }
     fInputData.fvStreamStopIndices[nStreams - 1] = fInputData.fvHits.size();
   }
 }
@@ -112,10 +100,12 @@ void DataManager::InitData()
 void DataManager::WriteInputData(const std::string& fileName) const
 {
   // Check current data object for consistency
-  if (!CheckInputData<constants::control::InputDataQaLevel>()) {
-    LOG(error) << "ca::DataManager: input data writer: attempt to write invalid input data object to file \""
-               << fileName << "\"";
-    return;
+  if constexpr (constants::control::InputDataQaLevel > 0) {
+    if (!CheckInputData<constants::control::InputDataQaLevel>()) {
+      LOG(error) << "ca::DataManager: input data writer: attempt to write invalid input data object to file \""
+                 << fileName << "\"";
+      return;
+    }
   }
 
   // Open output binary file
diff --git a/algo/ca/core/data/CaDataManager.h b/algo/ca/core/data/CaDataManager.h
index d210e52b9..b8dac8630 100644
--- a/algo/ca/core/data/CaDataManager.h
+++ b/algo/ca/core/data/CaDataManager.h
@@ -54,8 +54,9 @@ namespace cbm::algo::ca
     /// \param  nHits  Number of hits to reserve
     void ResetInputData(HitIndex_t nHits = 0) noexcept;
 
-    /// \brief  Pushes back a hit
-    /// \param  hit  An ca::Hit object
+    /// \brief  Pushes back a hit (with a data stream info)
+    /// \param  hit       An ca::Hit object
+    /// \param  streamId  Index of a data stream
     void PushBackHit(const Hit& hit, int64_t streamId)
     {
       if (fInputData.fvStreamStartIndices.size() == 0 || fLastStreamId != streamId) {  // new data stream
@@ -67,14 +68,14 @@ namespace cbm::algo::ca
       fInputData.fvHits.push_back(hit);
     }
 
+    /// \brief  Pushes back a hit
+    /// \param  hit       An ca::Hit object
+    void PushBackHit(const Hit& hit) { fInputData.fvHits.push_back(hit); }
+
     /// \brief  Sets the number of hit keys
     /// \param  nKeys  Number of hit keys
     void SetNhitKeys(int nKeys) { fInputData.fNhitKeys = nKeys; }
 
-    /// \brief Sends (moves) input data to an object (alternative method of data sending)
-    /// \param destination  Destination object of input data
-    bool SendInputData(InputData& destination);
-
     /// \brief Takes (moves) the instance of the input data object
     InputData&& TakeInputData();
 
diff --git a/algo/ca/core/utils/CaTrackingMonitor.h b/algo/ca/core/utils/CaTrackingMonitor.h
index f91de1d75..ffcee0c35 100644
--- a/algo/ca/core/utils/CaTrackingMonitor.h
+++ b/algo/ca/core/utils/CaTrackingMonitor.h
@@ -52,6 +52,11 @@ namespace cbm::algo::ca
   {
     TrackingChain,
       PrepareInputData,
+        PrepareStsHits,
+        PrepareTrdHits,
+        PrepareTofHits,
+        InputDataTransmission,
+        CaHitCreation,
       Tracking,
         PrepareTimeslice,
         TrackingThread,
@@ -103,6 +108,11 @@ namespace cbm::algo::ca
 
       SetTimerName(ETimer::TrackingChain, "tracking chain");
       SetTimerName(ETimer::PrepareInputData, "input data preparation");
+      SetTimerName(ETimer::PrepareStsHits, "STS hits preparation");
+      SetTimerName(ETimer::PrepareTrdHits, "TRD hits preparation");
+      SetTimerName(ETimer::PrepareTofHits, "TOF hits preparation");
+      SetTimerName(ETimer::InputDataTransmission, "input data transmission");
+      SetTimerName(ETimer::CaHitCreation, "CA hit creation");
       SetTimerName(ETimer::Tracking, "algorithm execution");
       SetTimerName(ETimer::PrepareTimeslice, "timeslice preparation");
       SetTimerName(ETimer::TrackingThread, "tracking on one thread");
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
new file mode 100644
index 000000000..330f70702
--- /dev/null
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   RecoEventSelectorMonitor.h
+/// \date   28.01.2025
+/// \brief  A monitor for reco event selector
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaMonitor.h"
+
+#include <boost/serialization/base_object.hpp>
+
+namespace cbm::algo::evselect
+{
+  /// \enum  ECounter
+  /// \brief Counter keys for the event selector monitor
+  enum class ECounter
+  {
+    EventsTotal,      ///< Total number of events processed
+    EventsNeStsHits,  ///< Events with not enough STS hits
+    EventsNeTofHits,  ///< Events with enough STS hits, but not enough TOF hits
+    EventsNeTracks,   ///< Events with enough hits, but not enough tracks
+    EventsSelected,   ///< Number of selected events
+    END
+  };
+
+  /// \enum  ETimer
+  /// \brief Timer keys for the event selector monitor
+  /* clang-format off */
+  enum class ETimer {
+    EventReconstruction,
+      StsHitFinder,
+      TofHitFinder,
+      TrdHitFinder,
+      TrackFinder,
+    END
+  };
+  /* clang-format on */
+
+  /// \brief Specification of ca::MonitorData for the event selector
+  using MonitorData_t = ca::MonitorData<ECounter, ETimer>;
+
+  /// \class Monitor
+  /// \brief A monitor for the event selector
+  class Monitor : public ca::Monitor<ECounter, ETimer> {
+   public:
+    /// \brief Default constructor
+    Monitor() : ca::Monitor<ECounter, ETimer>("Event-selector Monitor")
+    {
+      SetCounterName(ECounter::EventsTotal, "total events");
+      SetCounterName(ECounter::EventsNeStsHits, "events with not enough STS hits");
+      SetCounterName(ECounter::EventsNeTofHits, "events with enough STS hits, but not enough TOF hits");
+      SetCounterName(ECounter::EventsNeTracks, "events with enough STS and TOF hits, but not enough tracks");
+      SetCounterName(ECounter::EventsSelected, "selected events");
+
+      SetTimerName(ETimer::EventReconstruction, "event reconstruction");
+      SetTimerName(ETimer::StsHitFinder, "hit finding in STS");
+      SetTimerName(ETimer::TofHitFinder, "hit finding in TOF");
+      SetTimerName(ETimer::TrdHitFinder, "hit finding in TRD");
+      SetTimerName(ETimer::TrackFinder, "track finding");
+    }
+
+   private:
+    friend class boost::serialization::access;
+    template<typename Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<ca::Monitor<ECounter, ETimer>>(*this);
+    }
+  };
+
+}  // namespace cbm::algo::evselect
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index a990278ad..cfde9b3b4 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -253,16 +253,29 @@ void Reco::Init(const Options& opts)
   // Tracking
   if (Opts().Has(Step::Tracking)) {
     if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
-      fTracking = std::make_unique<TrackingChain>(fQaManager, "CaTimeslice");
+      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::Timeslice, fQaManager, "CaTimeslice");
     }
     else {
-      fTracking = std::make_unique<TrackingChain>();
+      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::Timeslice);
     }
     fTracking->RegisterSetup(pTrackingSetup);
     fTracking->SetContext(&fContext);
     fTracking->Init();
   }
 
+  if (fbReconstructDigiEvents) {
+    fEvSelectingMonitor.Reset();
+
+    if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
+      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent, fQaManager, "CaEvent");
+    }
+    else {
+      fTrackingEvent = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent);
+    }
+    fTrackingEvent->RegisterSetup(pTrackingSetup);
+    fTrackingEvent->SetContext(&fContext);
+    fTrackingEvent->Init();
+  
   // Initialize the QA manager
   if (fQaManager != nullptr) {
     fQaManager->Init();
@@ -403,12 +416,15 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
     }
 
     // --- Reconstruct and select digi events
-    if (Opts().ReconstructDigiEvents()) {
+    if (fbReconstructDigiEvents) {
       size_t nDiscardedEvents{0};
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsTotal, events.size());
       for (const auto& event : events) {
+        fEvSelectingMonitor.StartTimer(evselect::ETimer::EventReconstruction);
         if (!ReconstructEvent(event)) {
           ++nDiscardedEvents;
         }
+        fEvSelectingMonitor.StopTimer(evselect::ETimer::EventReconstruction);
       }
       L_(info) << "Rate of discarded events " << double(nDiscardedEvents) / events.size();
     }
@@ -461,8 +477,17 @@ void Reco::Finalize()
     fStsHitFinder->Finalize();
   }
   if (fTracking) {
+    L_(info) << "Track finding in a timeslice:";
     fTracking->Finalize();
   }
+  if (fTrackingEvent) {
+    L_(info) << "Track finding in digi events:";
+    fTrackingEvent->Finalize();
+  }
+  if (fbReconstructDigiEvents) {
+    L_(info) << fEvSelectingMonitor.ToString();
+  }
+
 
   if (Opts().Profiling() >= ProfilingSummary) {
     L_(info) << MakeReportSubtimers("Run Summary", fTimesliceTimesAcc) << "\n"
@@ -494,8 +519,11 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
   RecoResults recoEvent;
   //* STS hit reconstruction
   {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::StsHitFinder);
     auto stsResults = (*fStsHitFinder)(digiEvent.fSts);
-    if (stsResults.hits.NElements() < 2) {  // TODO: Provide a config for cuts (testing mode for now)
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::StsHitFinder);
+    if (stsResults.hits.NElements() < 4) {  // TODO: Provide a config for cuts (testing mode for now)
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeStsHits);
       return false;
     }
     recoEvent.stsHits = stsResults.hits;
@@ -503,9 +531,12 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
 
   //* TOF hit reconstruction
   {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TofHitFinder);
     auto [caldigis, calmonitor]          = (*fTofCalibrator)(digiEvent.fTof);
     auto [hits, hitmonitor, digiindices] = (*fTofHitFinder)(caldigis);
-    if (hits.NElements() < 1) {  // TODO: Provide a config for cuts (testing mode for now)
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TofHitFinder);
+    if (hits.NElements() < 2) {  // TODO: Provide a config for cuts (testing mode for now)
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTofHits);
       return false;
     }
     recoEvent.tofHits = std::move(hits);
@@ -514,16 +545,37 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
   //* TRD hit reconstruction
   {
     // FIXME: additional copy of digis, figure out how to pass 1d + 2d digis at once to hitfinder
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TrdHitFinder);
     const auto& digis1d = digiEvent.fTrd;
     const auto& digis2d = digiEvent.fTrd2d;
     PODVector<CbmTrdDigi> allDigis{};
     allDigis.reserve(digis1d.size() + digis2d.size());
     std::copy(digis1d.begin(), digis1d.end(), std::back_inserter(allDigis));
     std::copy(digis2d.begin(), digis2d.end(), std::back_inserter(allDigis));
-    auto trdResults   = (*fTrdHitfind)(allDigis);
+    auto trdResults = (*fTrdHitfind)(allDigis);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TrdHitFinder);
     recoEvent.trdHits = std::move(std::get<0>(trdResults));
   }
 
+  //* Tracking
+  {
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::TrackFinder);
+    TrackingChain::Input_t input{.stsHits = recoEvent.stsHits,
+                                 .tofHits = recoEvent.tofHits,
+                                 .trdHits = recoEvent.trdHits};
+    TrackingChain::Output_t output = fTrackingEvent->Run(input);
+    recoEvent.tracks               = std::move(output.tracks);
+    recoEvent.trackStsHitIndices   = std::move(output.stsHitIndices);
+    recoEvent.trackTofHitIndices   = std::move(output.tofHitIndices);
+    recoEvent.trackTrdHitIndices   = std::move(output.trdHitIndices);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::TrackFinder);
+    if (recoEvent.tracks.size() < 2) {  // Reject all events with less then two tracks
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTracks);
+      return false;
+    }
+  }
+
+  fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
   return true;
 }
 
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 5a2c62e63..1ab27d58d 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -5,6 +5,7 @@
 
 #include "AlgoTraits.h"
 #include "SubChain.h"
+#include "evselector/RecoEventSelectorMonitor.h"
 #include "global/RecoResults.h"
 
 #include <xpu/host.h>
@@ -181,7 +182,11 @@ namespace cbm::algo
     std::unique_ptr<evbuild::EventbuildChain> fEventBuild;
 
     // Tracking
-    std::unique_ptr<TrackingChain> fTracking;
+    std::unique_ptr<TrackingChain> fTracking;       ///< Tracking in timeslice
+    std::unique_ptr<TrackingChain> fTrackingEvent;  ///< Tracking in event
+
+    // Event selection
+    evselect::Monitor fEvSelectingMonitor;  ///< Monitor for event selecting
 
     // QA
     std::unique_ptr<qa::Manager> fQaManager;
diff --git a/algo/global/RecoResults.h b/algo/global/RecoResults.h
index eb64fa236..670927953 100644
--- a/algo/global/RecoResults.h
+++ b/algo/global/RecoResults.h
@@ -45,5 +45,6 @@ namespace cbm::algo
     ca::Vector<ca::Track> tracks;
     ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;
     ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTofHitIndices;
+    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTrdHitIndices;
   };
 }  // namespace cbm::algo
-- 
GitLab


From 2454920f311ff930686d3b850f9d19a63b2a8d87 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Sat, 1 Feb 2025 23:55:22 +0100
Subject: [PATCH 06/17] online: an empty chain for V0-finding

---
 algo/CMakeLists.txt           |  3 ++
 algo/global/Reco.cxx          | 13 ++++++++-
 algo/global/Reco.h            |  4 +++
 algo/kfp/KfpV0Finder.cxx      | 25 +++++++++++++++++
 algo/kfp/KfpV0Finder.h        | 47 +++++++++++++++++++++++++++++++
 algo/kfp/KfpV0FinderChain.cxx | 30 ++++++++++++++++++++
 algo/kfp/KfpV0FinderChain.h   | 53 +++++++++++++++++++++++++++++++++++
 core/data/CbmEventTriggers.h  |  3 ++
 8 files changed, 177 insertions(+), 1 deletion(-)
 create mode 100644 algo/kfp/KfpV0Finder.cxx
 create mode 100644 algo/kfp/KfpV0Finder.h
 create mode 100644 algo/kfp/KfpV0FinderChain.cxx
 create mode 100644 algo/kfp/KfpV0FinderChain.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index a2a052fa4..0524146a4 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -56,6 +56,7 @@ add_subdirectory(log)
 add_subdirectory(data)
 add_subdirectory(kf)
 add_subdirectory(ca)
+#add_subdirectory(kfp) # For KFParticleOnline
 
 # exclude unittests from being build inside the container
 if (NOT CBM_ONLINE_STANDALONE)
@@ -160,6 +161,8 @@ set(SRCS
   ca/TrackingSetup.cxx
   ca/TrackingChain.cxx
   ca/qa/CaQa.cxx
+  kfp/KfpV0Finder.cxx
+  kfp/KfpV0FinderChain.cxx
   kfp/KfpV0FinderConfig.cxx
 )
 
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index cfde9b3b4..7f7de2d06 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -22,6 +22,7 @@
 #include "ca/core/data/CaTrack.h"
 #include "compat/OpenMP.h"
 #include "evbuild/Config.h"
+#include "kfp/KfpV0FinderChain.h"
 #include "much/Unpack.h"
 #include "qa/QaManager.h"
 #include "qa/hitfind/BmonHitfindQa.h"
@@ -275,7 +276,12 @@ void Reco::Init(const Options& opts)
     fTrackingEvent->RegisterSetup(pTrackingSetup);
     fTrackingEvent->SetContext(&fContext);
     fTrackingEvent->Init();
-  
+
+    fV0Finder = std::make_unique<V0FinderChain>();
+    fV0Finder->SetContext(&fContext);
+    fV0Finder->Init();
+  }
+
   // Initialize the QA manager
   if (fQaManager != nullptr) {
     fQaManager->Init();
@@ -575,6 +581,11 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
     }
   }
 
+  //* V0-selector
+  {
+    auto triggers = fV0Finder->ProcessEvent(recoEvent);
+  }
+
   fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
   return true;
 }
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 1ab27d58d..eb8ba804a 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -21,6 +21,7 @@ namespace cbm::algo
   class HistogramSender;
   class Options;
   class TrackingChain;
+  class V0FinderChain;
 
   template<class M>
   struct UnpackMonitor;
@@ -185,6 +186,9 @@ namespace cbm::algo
     std::unique_ptr<TrackingChain> fTracking;       ///< Tracking in timeslice
     std::unique_ptr<TrackingChain> fTrackingEvent;  ///< Tracking in event
 
+    // V0-finding
+    std::unique_ptr<V0FinderChain> fV0Finder;  ///< V0-finding chain (in event or a bunch of events)
+
     // Event selection
     evselect::Monitor fEvSelectingMonitor;  ///< Monitor for event selecting
 
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
new file mode 100644
index 000000000..58ca42939
--- /dev/null
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -0,0 +1,25 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0Finder.cxx
+/// \date   01.02.2025
+/// \brief  A V0 finding algorithm (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0Finder.h"
+
+using cbm::algo::kfp::V0Finder;
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::Init() {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmEventTriggers V0Finder::Process(const RecoResults&)
+{
+  CbmEventTriggers res;
+  return res;
+}
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
new file mode 100644
index 000000000..d0a8a451a
--- /dev/null
+++ b/algo/kfp/KfpV0Finder.h
@@ -0,0 +1,47 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0Finder.h
+/// \date   01.02.2025
+/// \brief  A V0 finding algorithm
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmEventTriggers.h"
+#include "global/RecoResults.h"
+
+namespace cbm::algo::kfp
+{
+  /// \class V0Finder
+  /// \brief A V0-finding algorithm
+  class V0Finder {
+   public:
+    /// \brief Default constructor
+    V0Finder() = default;
+
+    /// \brief Copy constructor
+    V0Finder(const V0Finder&) = delete;
+
+    /// \brief Move constructor
+    V0Finder(V0Finder&&) = delete;
+
+    /// \brief Destructor
+    ~V0Finder() = default;
+
+    /// \brief Copy assignment operator
+    V0Finder& operator=(const V0Finder&) = delete;
+
+    /// \brief Move assignment operator
+    V0Finder& operator=(V0Finder&&) = delete;
+
+    /// \brief Initializes the instance (called in the beginning of the run)
+    void Init();
+
+    /// \brief Processes a reconstructed data sample, returns a collection of fired triggers
+    CbmEventTriggers Process(const RecoResults& recoEvent);
+
+   private:
+  };
+}  // namespace cbm::algo::kfp
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
new file mode 100644
index 000000000..a2a78be1c
--- /dev/null
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -0,0 +1,30 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderChain.cxx
+/// \date   01.02.2025
+/// \brief  A chain for V0 finding (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0FinderChain.h"
+
+#include "log/AlgoFairloggerCompat.h"
+
+using cbm::algo::V0FinderChain;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderChain::Finalize() {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderChain::Init()
+{
+  L_(info) << "Initializing the V0-finder chain ...";
+  L_(info) << "Initializing the V0-finder chain ... done";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+CbmEventTriggers V0FinderChain::ProcessEvent(const RecoResults& recoEvent) { return fV0Finder.Process(recoEvent); }
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
new file mode 100644
index 000000000..e3582f931
--- /dev/null
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderChain.h
+/// \date   01.02.2025
+/// \brief  A chain for V0 finding
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmEventTriggers.h"
+#include "base/SubChain.h"
+#include "global/RecoResults.h"
+#include "kfp/KfpV0Finder.h"
+
+namespace cbm::algo
+{
+  /// \class V0FinderChain
+  /// \brief A chain for the V0 finder
+  class V0FinderChain : public SubChain {
+   public:
+    /// \brief Default constructor
+    V0FinderChain() = default;
+
+    /// \brief Copy constructor
+    V0FinderChain(const V0FinderChain&) = delete;
+
+    /// \brief Move constructor
+    V0FinderChain(V0FinderChain&&) = delete;
+
+    /// \brief Destructor
+    ~V0FinderChain() = default;
+
+    /// \brief Copy assignment operator
+    V0FinderChain& operator=(const V0FinderChain&) = delete;
+
+    /// \brief Move assignment operator
+    V0FinderChain& operator=(V0FinderChain&&) = delete;
+
+    /// \brief Finalizes the instance (called in the end of the run)
+    void Finalize();
+
+    /// \brief Initializes the instance (called in the beginning of the run)
+    void Init();
+
+    /// \brief Processes an event, returns a collection of fired triggers
+    CbmEventTriggers ProcessEvent(const RecoResults& recoEvent);
+
+   private:
+    kfp::V0Finder fV0Finder;  ///< Instance of the V0-finding algorithm
+  };
+}  // namespace cbm::algo
diff --git a/core/data/CbmEventTriggers.h b/core/data/CbmEventTriggers.h
index ce1b8de53..365d286e5 100644
--- a/core/data/CbmEventTriggers.h
+++ b/core/data/CbmEventTriggers.h
@@ -9,6 +9,9 @@
 
 #pragma once
 
+#include <cstdint>
+#include <string>
+
 #if !defined(NO_ROOT) && !XPU_IS_HIP_CUDA
 #include <Rtypes.h>  // for ClassDef
 #endif
-- 
GitLab


From c05f21ff66b904712ae4796e298f202ffe677a96 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 3 Feb 2025 20:39:35 +0100
Subject: [PATCH 07/17] online: online interface for the KFParticle and
 intialization of V0Finder

---
 algo/CMakeLists.txt                        |  3 +
 algo/evselector/RecoEventSelectorMonitor.h |  6 +-
 algo/global/ParFiles.cxx                   |  6 ++
 algo/global/ParFiles.h                     |  4 +
 algo/kfp/CMakeLists.txt                    |  1 +
 algo/kfp/KfpV0Finder.cxx                   |  1 -
 algo/kfp/KfpV0Finder.h                     | 87 ++++++++++++++++++++++
 algo/kfp/KfpV0FinderChain.cxx              | 83 ++++++++++++++++++++-
 algo/kfp/interface/CMakeLists.txt          | 42 +++++++++++
 9 files changed, 226 insertions(+), 7 deletions(-)
 create mode 100644 algo/kfp/CMakeLists.txt
 create mode 100644 algo/kfp/interface/CMakeLists.txt

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 0524146a4..6ba7a06c0 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -52,6 +52,7 @@ if (CBM_ONLINE_STANDALONE)
   add_subdirectory(../external external)
 endif()
 
+add_subdirectory(kfp)
 add_subdirectory(log)
 add_subdirectory(data)
 add_subdirectory(kf)
@@ -226,6 +227,7 @@ target_link_libraries(Algo
             external::fles_monitoring
             cppzmq
             poolstl
+  PRIVATE   CbmKFParticleOnlineInterface 
 )
 target_compile_definitions(Algo PUBLIC NO_ROOT)
 xpu_attach(Algo ${DEVICE_SRCS})
@@ -299,6 +301,7 @@ if (NOT CBM_ONLINE_STANDALONE)
               external::fles_monitoring
               cppzmq
               poolstl
+  PRIVATE   CbmKFParticleOfflineInterface 
   )
   xpu_attach(AlgoOffline ${DEVICE_SRCS})
 
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
index 330f70702..41bc2ff13 100644
--- a/algo/evselector/RecoEventSelectorMonitor.h
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -51,9 +51,9 @@ namespace cbm::algo::evselect
     Monitor() : ca::Monitor<ECounter, ETimer>("Event-selector Monitor")
     {
       SetCounterName(ECounter::EventsTotal, "total events");
-      SetCounterName(ECounter::EventsNeStsHits, "events with not enough STS hits");
-      SetCounterName(ECounter::EventsNeTofHits, "events with enough STS hits, but not enough TOF hits");
-      SetCounterName(ECounter::EventsNeTracks, "events with enough STS and TOF hits, but not enough tracks");
+      SetCounterName(ECounter::EventsNeStsHits, "events discarded by N STS hits");
+      SetCounterName(ECounter::EventsNeTofHits, "events discarded by N TOF hits");
+      SetCounterName(ECounter::EventsNeTracks, "events discarded by N tracks");
       SetCounterName(ECounter::EventsSelected, "selected events");
 
       SetTimerName(ETimer::EventReconstruction, "event reconstruction");
diff --git a/algo/global/ParFiles.cxx b/algo/global/ParFiles.cxx
index ac2c3b410..38773a0e8 100644
--- a/algo/global/ParFiles.cxx
+++ b/algo/global/ParFiles.cxx
@@ -45,6 +45,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "TrdHitfinder2DPar_mcbm2022.yaml";
 
       ca.mainConfig = "TrackingChainConfig_mcbm2022.yaml";
+
+      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
       break;
 
     case Setup::mCBM2024_03:
@@ -68,6 +70,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "TrdHitfinder2DPar_mcbm2024.yaml";
 
       ca.mainConfig = "TrackingChainConfig_mcbm2024.yaml";
+
+      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
       break;
 
     case Setup::mCBM2024_05:
@@ -91,6 +95,8 @@ ParFiles::ParFiles(uint32_t runId)
       trd.hitfinder2d = "mcbm2024_05/TrdHitfinder2DPar.yaml";
 
       ca.mainConfig = "mcbm2024_05/TrackingChainConfig.yaml";
+
+      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
       break;
 
     case Setup::mCBM2025_02:
diff --git a/algo/global/ParFiles.h b/algo/global/ParFiles.h
index 691e27b39..0fe7b3094 100644
--- a/algo/global/ParFiles.h
+++ b/algo/global/ParFiles.h
@@ -54,6 +54,10 @@ namespace cbm::algo
     struct {
       fs::path mainConfig;
     } ca;
+
+    struct {
+      fs::path V0FinderConfig;
+    } kfp;
   };
 
 }  // namespace cbm::algo
diff --git a/algo/kfp/CMakeLists.txt b/algo/kfp/CMakeLists.txt
new file mode 100644
index 000000000..cc41f095b
--- /dev/null
+++ b/algo/kfp/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(interface)
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index 58ca42939..52a8f0d03 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -11,7 +11,6 @@
 
 using cbm::algo::kfp::V0Finder;
 
-
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void V0Finder::Init() {}
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index d0a8a451a..5240085b8 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -10,8 +10,12 @@
 #pragma once
 
 #include "CbmEventTriggers.h"
+#include "KFParticleTopoReconstructor.h"
 #include "global/RecoResults.h"
 
+#include <memory>
+
+
 namespace cbm::algo::kfp
 {
   /// \class V0Finder
@@ -36,12 +40,95 @@ namespace cbm::algo::kfp
     /// \brief Move assignment operator
     V0Finder& operator=(V0Finder&&) = delete;
 
+    /// \brief  Adds particle to reconstruction list
+    /// \param  pdg  A PDG code of the particle to be reconstructed
+    void AddDecayToReconstructionList(int pdg) { GetKFParticleFinder()->AddDecayToReconstructionList(pdg); }
+
+    /// \brief  Mutable access to the KfParticleFinder of the run topology reconstructor
+    KFParticleFinder* GetKFParticleFinder() { return fpTopoReconstructor->GetKFParticleFinder(); }
+
+    /// \brief  Constant access to the KfParticleFinder of the run topology reconstructor
+    const KFParticleFinder* GetKFParticleFinder() const { return fpTopoReconstructor->GetKFParticleFinder(); }
+
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
     /// \brief Processes a reconstructed data sample, returns a collection of fired triggers
     CbmEventTriggers Process(const RecoResults& recoEvent);
 
+    /// \brief  Sets minimal pion DCA to primary vertex
+    /// \param  dca  DCA [cm]
+    void SetMinPionDca(double dca) { fMinPionDca = dca; }
+
+    /// \brief  Sets minimal proton DCA to primary vertex
+    /// \param  dca  DCA [cm]
+    void SetMinProtonDca(double dca) { fMinProtonDca = dca; }
+
+    /// \brief  Sets pion velocity range
+    /// \param  vMin  Minimal velocity [cm/ns]
+    /// \param  vMax  Maximal velocity [cm/ns]
+    void SetPionVelocityRange(double vMin, double vMax)
+    {
+      fMinBetaPion = vMin / kSpeedOfLight;
+      fMaxBetaPion = vMax / kSpeedOfLight;
+    }
+
+    /// \brief  Sets proton velocity range
+    /// \param  vMin  Minimal velocity [cm/ns]
+    /// \param  vMax  Maximal velocity [cm/ns]
+    void SetProtonVelocityRange(double vMin, double vMax)
+    {
+      fMinBetaProton = vMin / kSpeedOfLight;
+      fMaxBetaProton = vMax / kSpeedOfLight;
+    }
+
+    /// \brief  Sets the assigned PDG for primary particles
+    /// \param  pdg  PDG code of the particle
+    void SetPrimaryAssignedPdg(int pdg) { fPrimaryAssignedPdg = pdg; }
+
+    /// \brief  Assignes an uncertainty to the momentum measurement
+    /// \param  uncertainty  Relative uncertainty ( = sqrt(var(q/p)) / (q/p))
+    void SetQpAssignedUncertainty(double uncertainty) { fQpAssignedUncertainty = uncertainty; }
+
+    /// \brief  Sets an offset to t0
+    /// \param  offset  An offset [ns]
+    void SetTzeroOffset(double offset) { fTzeroOffset = offset; }
+
+    //* Specific parameters for the KFParticleFinder
+    /// \brief  Sets cut on the distance to the primary vertex from the decay vertex
+    /// \param  cut  Cut value [cm]
+    void SetLCut(float cut) { GetKFParticleFinder()->SetLCut(cut); }
+
+    /// \brief  Sets cut on \f$\chi^2_{prim}\f$ of each track for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetChiPrimaryCut2D(float cut) { GetKFParticleFinder()->SetChiPrimaryCut2D(cut); }
+
+    /// \brief  Sets cut on \f$\chi^2_{geo}\f$ for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetChi2Cut2D(float cut) { GetKFParticleFinder()->SetChi2Cut2D(cut); }
+
+    /// \brief  Sets cut on \f$l/\Delta l\f$ for 2-daughter decays
+    /// \param  cut  Cut value
+    void SetLdLCut2D(float cut) { GetKFParticleFinder()->SetLdLCut2D(cut); }
+
    private:
+    //* Physical constants
+    static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
+    static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
+    static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
+
+    /// \brief An instance of the topology reconstructor
+    std::unique_ptr<KFParticleTopoReconstructor> fpTopoReconstructor{std::make_unique<KFParticleTopoReconstructor>()};
+
+    //* Different run-time cuts and flags (TODO: define in a config)
+    double fTzeroOffset{0.};             ///< Offset for T0
+    double fMinPionDca{1.5};             ///< Minimum DCA to PV for pions
+    double fMinProtonDca{0.5};           ///< Minimum DCA to PV for protons
+    double fQpAssignedUncertainty{0.1};  ///< Assigned relative uncertainty for q/p estimation
+    int fPrimaryAssignedPdg{321};        ///< Assigned PDG hypothesis for primary particles
+    double fMinBetaProton{0.};           ///< Minimal proton velocity (beta) [c]
+    double fMaxBetaProton{1.};           ///< Maximal proton velocity (beta) [c]
+    double fMinBetaPion{0.};             ///< Minimal proton velocity (beta) [c]
+    double fMaxBetaPion{1.};             ///< Maximal proton velocity (beta) [c]
   };
 }  // namespace cbm::algo::kfp
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
index a2a78be1c..1fb1a7a9d 100644
--- a/algo/kfp/KfpV0FinderChain.cxx
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -9,7 +9,12 @@
 
 #include "kfp/KfpV0FinderChain.h"
 
+#include "global/ParFiles.h"
+#include "kfp/KfpV0FinderConfig.h"
 #include "log/AlgoFairloggerCompat.h"
+#include "yaml/Yaml.h"
+
+#include <sstream>
 
 using cbm::algo::V0FinderChain;
 
@@ -20,9 +25,81 @@ void V0FinderChain::Finalize() {}
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void V0FinderChain::Init()
-{
-  L_(info) << "Initializing the V0-finder chain ...";
-  L_(info) << "Initializing the V0-finder chain ... done";
+try {
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ...";
+
+  // ----- Read configuration file
+  ParFiles parFiles(Opts().RunId());
+  auto config = yaml::ReadFromFile<kfp::V0FinderConfig>(Opts().ParamsDir() / parFiles.kfp.V0FinderConfig);
+  L_(info) << config.ToString();
+
+  //* Check the V0 type
+  if (config.reconstructPdg != 3122) {  // At the moment only Lambda analysis is possible
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: at the moment only lambda finding is possible. Provided PDG: " << config.reconstructPdg;
+    throw std::runtime_error(msg.str());
+  }
+
+  //* Define daughter particles
+  auto& particles{config.cuts.particles};
+  int iPion{-1};
+  int iProton{-1};
+  for (int iParticle = 0; iParticle < int(particles.size()); ++iParticle) {
+    const auto& particle  = particles[iParticle];
+    auto CheckOutParticle = [&](int pdg, int& iParticleToFind) {
+      if (particle.pdg == pdg) {
+        if (iParticleToFind == -1) {
+          iParticleToFind = iParticle;
+        }
+        else {
+          std::stringstream msg;
+          msg << "kfp::V0FinderChain: entry for pdg= " << pdg << " is defined more then one time in the ";
+          msg << "config.cuts.particles";
+          throw std::runtime_error(msg.str());
+        }
+      }
+    };
+    CheckOutParticle(-211, iPion);
+    CheckOutParticle(2212, iProton);
+  }
+  if (iProton == -1 || iPion == -1) {
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: config cuts/particles: either pion or proton settings are not found";
+    throw std::runtime_error(msg.str());
+  }
+  const auto& pion{particles[iPion]};
+  const auto& proton{particles[iProton]};
+
+  // ----- Set the V0-finder properties
+  // TODO: In future, there are will be a several instances of the V0Finder, each for a particular thread
+  {
+    //* Set particle PID properties
+    fV0Finder.SetMinPionDca(pion.minDca);
+    fV0Finder.SetMinProtonDca(proton.minDca);
+    fV0Finder.SetPionVelocityRange(pion.minVelocity, pion.maxVelocity);
+    fV0Finder.SetProtonVelocityRange(proton.minVelocity, proton.maxVelocity);
+
+    //* Set KFParticleFinder properties
+    auto& kfpCuts{config.cuts.kfp};
+    fV0Finder.SetLdLCut2D(kfpCuts.minDecayLDL);
+    fV0Finder.SetLCut(kfpCuts.minDecayLength);
+    fV0Finder.SetChi2Cut2D(kfpCuts.maxChi2NdfGeo);
+    fV0Finder.SetChiPrimaryCut2D(kfpCuts.maxChi2NdfPrim);
+
+    //* Set other properties
+    fV0Finder.SetTzeroOffset(config.tZeroOffset);
+    fV0Finder.SetQpAssignedUncertainty(config.qpAssignedUncertainty);
+    fV0Finder.AddDecayToReconstructionList(config.reconstructPdg);
+    fV0Finder.SetPrimaryAssignedPdg(config.primaryAssignedPdg);
+
+    //* Init the V0 finder
+    fV0Finder.Init();
+  }
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... done";
+}
+catch (const std::exception& err) {
+  L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... failed. Reason: " << err.what();
+  throw std::runtime_error("initialization of V0-finder chain failed");
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
new file mode 100644
index 000000000..8b037a70e
--- /dev/null
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -0,0 +1,42 @@
+# The script creates two libraries:
+#  1) CbmKFParticleOnlineInterface -- a specific KFParticle library interface for the CBM online reconstruction;
+#  2) CbmKFParticleOfflineInterface -- a specific interface for the offline reconstruction in CBM.
+# The both libraries contain only the KFParticle core and do not include KFParticleTest or KFParticlePerformance.
+
+set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
+
+set(SRCS
+  ${KFP_SOURCE_DIR}/KFParticle.cxx
+  ${KFP_SOURCE_DIR}/KFParticleBase.cxx
+  ${KFP_SOURCE_DIR}/KFParticleBaseSIMD.cxx
+  ${KFP_SOURCE_DIR}/KFParticleDatabase.cxx
+  ${KFP_SOURCE_DIR}/KFParticleFinder.cxx
+  ${KFP_SOURCE_DIR}/KFParticlePVReconstructor.cxx
+  ${KFP_SOURCE_DIR}/KFParticleSIMD.cxx
+  ${KFP_SOURCE_DIR}/KFParticleTopoReconstructor.cxx
+  ${KFP_SOURCE_DIR}/KFPEmcCluster.cxx
+  ${KFP_SOURCE_DIR}/KFPTrack.cxx
+  ${KFP_SOURCE_DIR}/KFPTrackVector.cxx
+  ${KFP_SOURCE_DIR}/KFPVertex.cxx
+  ${KFP_SOURCE_DIR}/KFVertex.cxx
+)
+
+### CbmKFParticleOnlineInterface
+add_library(CbmKFParticleOnlineInterface INTERFACE)
+target_include_directories(CbmKFParticleOnlineInterface INTERFACE)
+target_compile_definitions(CbmKFParticleOnlineInterface 
+  INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
+target_link_libraries(CbmKFParticleOnlineInterface 
+  INTERFACE ROOT::Core KFParticle)
+install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
+
+if(NOT CBM_ONLINE_STANDALONE)
+### CbmKFParticleOfflineInterface
+  add_library(CbmKFParticleOfflineInterface INTERFACE)
+  target_include_directories(CbmKFParticleOfflineInterface INTERFACE)
+  target_compile_definitions(CbmKFParticleOfflineInterface 
+    INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
+  target_link_libraries(CbmKFParticleOfflineInterface 
+    INTERFACE ROOT::Core KFParticle)
+  install(TARGETS CbmKFParticleOfflineInterface DESTINATION lib)
+endif()
-- 
GitLab


From 933f7d5481ac9d611e6212cff39b9c5eece4cdb1 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 3 Feb 2025 23:18:49 +0100
Subject: [PATCH 08/17] online V0 finder: adding a monitor

---
 algo/ca/TrackingChain.cxx      |  3 +-
 algo/ca/TrackingChain.h        |  3 +-
 algo/ca/core/utils/CaMonitor.h | 12 +++--
 algo/global/Reco.cxx           |  1 +
 algo/kfp/KfpV0Finder.cxx       |  3 ++
 algo/kfp/KfpV0Finder.h         |  8 +++-
 algo/kfp/KfpV0FinderChain.cxx  | 37 +++++++++------
 algo/kfp/KfpV0FinderChain.h    | 16 +++++--
 algo/kfp/KfpV0FinderMonitor.h  | 86 ++++++++++++++++++++++++++++++++++
 9 files changed, 143 insertions(+), 26 deletions(-)
 create mode 100644 algo/kfp/KfpV0FinderMonitor.h

diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index d3ee72ab7..5ce7a4e51 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -40,7 +40,8 @@ using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager, std::string_view name)
+TrackingChain::TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager,
+                             std::string_view name)
   : fQa(Qa(qaManager, name))
   , fRecoMode(recoMode)
 {
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 5d30319fb..39ba48e26 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -44,7 +44,8 @@ namespace cbm::algo
     /// \param recoMode     Reconstruction mode
     /// \param pManager a QA-manager
     /// \param name A name of the task (histograms directory)
-    TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr, std::string_view name = "");
+    TrackingChain(ECbmRecoMode recoMode, const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr,
+                  std::string_view name = "");
 
     /// \struct Input_t
     /// \brief  Input to the TrackingChain
diff --git a/algo/ca/core/utils/CaMonitor.h b/algo/ca/core/utils/CaMonitor.h
index 389b196fe..6d26469cf 100644
--- a/algo/ca/core/utils/CaMonitor.h
+++ b/algo/ca/core/utils/CaMonitor.h
@@ -171,12 +171,13 @@ namespace cbm::algo::ca
     using std::setfill;
     using std::setw;
     std::stringstream msg;
-    constexpr size_t widthKey = 30;
     constexpr size_t width    = 24;
+    auto Cmp                  = [](const std::string& l, const std::string& r) { return l.size() < r.size(); };
     msg << "\n===== Monitor: " << fsName << "\n";
     if constexpr (!std::is_same_v<ETimerKey, EDummy>) {
+      size_t widthKeyTimer{std::max_element(faTimerNames.begin(), faTimerNames.end(), Cmp)->size() + 1};
       msg << "\n----- Timers:\n";
-      msg << setw(widthKey) << left << "Key" << ' ';
+      msg << setw(widthKeyTimer) << left << "Key" << ' ';
       msg << setw(width) << left << "N Calls" << ' ';
       msg << setw(width) << left << "Average [s]" << ' ';
       msg << setw(width) << left << "Min [s]" << ' ';
@@ -185,7 +186,7 @@ namespace cbm::algo::ca
       msg << right;
       for (int iKey = 0; iKey < fMonitorData.GetNofTimers(); ++iKey) {
         const Timer& timer = fMonitorData.GetTimer(static_cast<ETimerKey>(iKey));
-        msg << setw(widthKey) << faTimerNames[iKey] << ' ';
+        msg << setw(widthKeyTimer) << faTimerNames[iKey] << ' ';
         msg << setw(width) << timer.GetNofCalls() << ' ';
         msg << setw(width) << timer.GetAverage() << ' ';
         msg << setw(width) << timer.GetMin() << ' ';
@@ -195,7 +196,8 @@ namespace cbm::algo::ca
     }
 
     msg << "\n----- Counters:\n";
-    msg << setw(widthKey) << left << "Key" << ' ';
+    size_t widthKeyCounter{std::max_element(faCounterNames.begin(), faCounterNames.end(), Cmp)->size() + 1};
+    msg << setw(widthKeyCounter) << left << "Key" << ' ';
     msg << setw(width) << left << "Total" << ' ';
     for (auto key : fvCounterRatioKeys) {
       msg << setw(width) << left << std::string("per ") + faCounterNames[key] << ' ';
@@ -203,7 +205,7 @@ namespace cbm::algo::ca
     msg << '\n';
     for (int iKey = 0; iKey < fMonitorData.GetNofCounters(); ++iKey) {
       auto counterValue = fMonitorData.GetCounterValue(static_cast<ECounterKey>(iKey));
-      msg << setw(widthKey) << left << faCounterNames[iKey] << ' ';
+      msg << setw(widthKeyCounter) << left << faCounterNames[iKey] << ' ';
       msg << setw(width) << right << counterValue << ' ';
       for (auto keyDen : fvCounterRatioKeys) {
         auto ratio = static_cast<double>(counterValue) / fMonitorData.GetCounterValue(keyDen);
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 7f7de2d06..e9d10bdd3 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -491,6 +491,7 @@ void Reco::Finalize()
     fTrackingEvent->Finalize();
   }
   if (fbReconstructDigiEvents) {
+    fV0Finder->Finalize();
     L_(info) << fEvSelectingMonitor.ToString();
   }
 
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index 52a8f0d03..5677dc5f6 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -20,5 +20,8 @@ void V0Finder::Init() {}
 CbmEventTriggers V0Finder::Process(const RecoResults&)
 {
   CbmEventTriggers res;
+  fEventMonitor.Reset();
+  fEventMonitor.StartTimer(ETimer::Event);
+  fEventMonitor.StopTimer(ETimer::Event);
   return res;
 }
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index 5240085b8..e34e543d2 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -12,6 +12,7 @@
 #include "CbmEventTriggers.h"
 #include "KFParticleTopoReconstructor.h"
 #include "global/RecoResults.h"
+#include "kfp/KfpV0FinderMonitor.h"
 
 #include <memory>
 
@@ -50,10 +51,13 @@ namespace cbm::algo::kfp
     /// \brief  Constant access to the KfParticleFinder of the run topology reconstructor
     const KFParticleFinder* GetKFParticleFinder() const { return fpTopoReconstructor->GetKFParticleFinder(); }
 
+    /// \brief  Gets monitor data
+    const V0FinderMonitorData_t& GetEventMonitor() const { return fEventMonitor; }
+
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
-    /// \brief Processes a reconstructed data sample, returns a collection of fired triggers
+    /// \brief  Processes a reconstructed data sample, returns a collection of fired triggers
     CbmEventTriggers Process(const RecoResults& recoEvent);
 
     /// \brief  Sets minimal pion DCA to primary vertex
@@ -120,6 +124,8 @@ namespace cbm::algo::kfp
     /// \brief An instance of the topology reconstructor
     std::unique_ptr<KFParticleTopoReconstructor> fpTopoReconstructor{std::make_unique<KFParticleTopoReconstructor>()};
 
+    V0FinderMonitorData_t fEventMonitor;  ///< Monitor data instance (per event)
+
     //* Different run-time cuts and flags (TODO: define in a config)
     double fTzeroOffset{0.};             ///< Offset for T0
     double fMinPionDca{1.5};             ///< Minimum DCA to PV for pions
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
index 1fb1a7a9d..621f4d0d8 100644
--- a/algo/kfp/KfpV0FinderChain.cxx
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -20,7 +20,7 @@ using cbm::algo::V0FinderChain;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void V0FinderChain::Finalize() {}
+void V0FinderChain::Finalize() { L_(info) << fMonitorRun.ToString(); }
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
@@ -74,26 +74,26 @@ try {
   // TODO: In future, there are will be a several instances of the V0Finder, each for a particular thread
   {
     //* Set particle PID properties
-    fV0Finder.SetMinPionDca(pion.minDca);
-    fV0Finder.SetMinProtonDca(proton.minDca);
-    fV0Finder.SetPionVelocityRange(pion.minVelocity, pion.maxVelocity);
-    fV0Finder.SetProtonVelocityRange(proton.minVelocity, proton.maxVelocity);
+    fFinder.SetMinPionDca(pion.minDca);
+    fFinder.SetMinProtonDca(proton.minDca);
+    fFinder.SetPionVelocityRange(pion.minVelocity, pion.maxVelocity);
+    fFinder.SetProtonVelocityRange(proton.minVelocity, proton.maxVelocity);
 
     //* Set KFParticleFinder properties
     auto& kfpCuts{config.cuts.kfp};
-    fV0Finder.SetLdLCut2D(kfpCuts.minDecayLDL);
-    fV0Finder.SetLCut(kfpCuts.minDecayLength);
-    fV0Finder.SetChi2Cut2D(kfpCuts.maxChi2NdfGeo);
-    fV0Finder.SetChiPrimaryCut2D(kfpCuts.maxChi2NdfPrim);
+    fFinder.SetLdLCut2D(kfpCuts.minDecayLDL);
+    fFinder.SetLCut(kfpCuts.minDecayLength);
+    fFinder.SetChi2Cut2D(kfpCuts.maxChi2NdfGeo);
+    fFinder.SetChiPrimaryCut2D(kfpCuts.maxChi2NdfPrim);
 
     //* Set other properties
-    fV0Finder.SetTzeroOffset(config.tZeroOffset);
-    fV0Finder.SetQpAssignedUncertainty(config.qpAssignedUncertainty);
-    fV0Finder.AddDecayToReconstructionList(config.reconstructPdg);
-    fV0Finder.SetPrimaryAssignedPdg(config.primaryAssignedPdg);
+    fFinder.SetTzeroOffset(config.tZeroOffset);
+    fFinder.SetQpAssignedUncertainty(config.qpAssignedUncertainty);
+    fFinder.AddDecayToReconstructionList(config.reconstructPdg);
+    fFinder.SetPrimaryAssignedPdg(config.primaryAssignedPdg);
 
     //* Init the V0 finder
-    fV0Finder.Init();
+    fFinder.Init();
   }
   L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... done";
 }
@@ -104,4 +104,11 @@ catch (const std::exception& err) {
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-CbmEventTriggers V0FinderChain::ProcessEvent(const RecoResults& recoEvent) { return fV0Finder.Process(recoEvent); }
+V0FinderChain::EventOutput V0FinderChain::ProcessEvent(const RecoResults& recoEvent)
+{
+  EventOutput res;
+  res.triggers = fFinder.Process(recoEvent);
+  res.monitor  = fFinder.GetEventMonitor();
+  fMonitorRun.AddMonitorData(res.monitor);
+  return res;
+}
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
index e3582f931..2be7ddf5d 100644
--- a/algo/kfp/KfpV0FinderChain.h
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -13,6 +13,7 @@
 #include "base/SubChain.h"
 #include "global/RecoResults.h"
 #include "kfp/KfpV0Finder.h"
+#include "kfp/KfpV0FinderMonitor.h"
 
 namespace cbm::algo
 {
@@ -20,6 +21,13 @@ namespace cbm::algo
   /// \brief A chain for the V0 finder
   class V0FinderChain : public SubChain {
    public:
+    /// \struct EventOutput
+    /// \brief  Output from the event
+    struct EventOutput {
+      CbmEventTriggers triggers;           ///< Set of triggers
+      kfp::V0FinderMonitorData_t monitor;  ///< Monitor
+    };
+
     /// \brief Default constructor
     V0FinderChain() = default;
 
@@ -44,10 +52,12 @@ namespace cbm::algo
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
-    /// \brief Processes an event, returns a collection of fired triggers
-    CbmEventTriggers ProcessEvent(const RecoResults& recoEvent);
+    /// \brief  Processes an event, returns a collection of fired triggers
+    EventOutput ProcessEvent(const RecoResults& recoEvent);
+
 
    private:
-    kfp::V0Finder fV0Finder;  ///< Instance of the V0-finding algorithm
+    kfp::V0Finder fFinder;             ///< Instance of the V0-finding algorithm
+    kfp::V0FinderMonitor fMonitorRun;  ///< Monitor per run
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0FinderMonitor.h b/algo/kfp/KfpV0FinderMonitor.h
new file mode 100644
index 000000000..1f63bb246
--- /dev/null
+++ b/algo/kfp/KfpV0FinderMonitor.h
@@ -0,0 +1,86 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderMonitor.h
+/// \date   03.02.2025
+/// \brief  A monitor for the V0Finder
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaMonitor.h"
+
+namespace cbm::algo::kfp
+{
+  /// \enum  ECounter
+  /// \brief Counter keys for the V0FinderMonitor
+  enum class ECounter
+  {
+    TracksTotal,                ///< Total number of tracks
+    TracksSelected,             ///< Tracks, which satisfy topology PID applicability
+    TracksInfiniteParam,        ///< Tracks, which have infinite parameters
+    TracksWoTofHits,            ///< Tracks, which have no TOF hits
+    TracksWNegativeTofHitTime,  ///< Tracks, the last TOF hit of which has a negative time (it's time is less then the t0)
+    TracksWoStsHits,            ///< Tracks, which have no STS hits
+    TracksWoPid,                ///< Tracks, which has undefined PID
+    TracksWoMomentum,           ///< Tracks, which has no momentum
+    TracksWUnphysicalBeta,      ///< Tracks with beta > 1
+    Pions,                      ///< Number of pion-candidates
+    Protons,                    ///< Number of proton-candidates
+    EventsTotal,                ///< Total number of events
+    EventsWoTzero,              ///< Number of events with undefined t-zero
+    EventsLambdaCand,           ///< Events with at least one pion and one proton candidate
+    KfpEventsLambdaCand,        ///< Events with lambda-candidates in KF-particle
+    KfpLambdaCandidates,        ///< Number of lambda-candidates
+    END
+  };
+
+  /// \enum  ETimer
+  /// \brief Timer keys for the V0FinderMonitor
+  enum class ETimer
+  {
+    Event,  ///< Timer to process a single event
+    END
+  };
+
+  /// \brief Specification of ca::MonitorData for the V0Finder
+  using V0FinderMonitorData_t = ca::MonitorData<ECounter, ETimer>;
+
+  /// \class Monitor
+  /// \brief A monitor for the V0Finder
+  class V0FinderMonitor : public ca::Monitor<ECounter, ETimer> {
+   public:
+    /// \brief Default constructor
+    V0FinderMonitor() : ca::Monitor<ECounter, ETimer>("V0 finder monitor")
+    {
+      SetCounterName(ECounter::TracksTotal, "all tracks");
+      SetCounterName(ECounter::TracksSelected, "pre-selected tracks");
+      SetCounterName(ECounter::TracksInfiniteParam, "tracks satisfying PID selection");
+      SetCounterName(ECounter::TracksWoTofHits, "tracks w/o TOF hits");
+      SetCounterName(ECounter::TracksWNegativeTofHitTime, "tracks w/ negative TOF time");
+      SetCounterName(ECounter::TracksWoStsHits, "tracks w/o STS hits");
+      SetCounterName(ECounter::TracksWoPid, "tracks w/o PID");
+      SetCounterName(ECounter::TracksWoMomentum, "tracks w/o momentum");
+      SetCounterName(ECounter::TracksWUnphysicalBeta, "tracks w/ beta > 1");
+      SetCounterName(ECounter::Pions, "pion candidates");
+      SetCounterName(ECounter::Protons, "proton candidates");
+      SetCounterName(ECounter::EventsTotal, "all events");
+      SetCounterName(ECounter::EventsWoTzero, "events w/o t0");
+      SetCounterName(ECounter::EventsLambdaCand, "events passed to KFP");
+      SetCounterName(ECounter::KfpEventsLambdaCand, "events w/ lambda candidates");
+      SetCounterName(ECounter::KfpLambdaCandidates, "lambda candidates");
+
+      SetTimerName(ETimer::Event, "event processing");
+    }
+
+   private:
+    friend class boost::serialization::access;
+    template<typename Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<ca::Monitor<ECounter, ETimer>>(*this);
+    }
+  };
+
+}  // namespace cbm::algo::kfp
-- 
GitLab


From 172082af59152be0aab0b73006a01ef0a8fcd5e4 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Tue, 11 Feb 2025 05:59:27 +0100
Subject: [PATCH 09/17] online: Adding BMON hit-finder to event reconstruction

---
 algo/global/Reco.cxx        | 41 +++++++++++++++++++++++--------------
 algo/global/RecoResults.h   |  2 ++
 algo/kfp/KfpV0FinderChain.h |  1 +
 3 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index e9d10bdd3..0f933400d 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -212,21 +212,6 @@ void Reco::Init(const Options& opts)
     fStsHitFinder->SetParameters(hitFinderPars);
   }
 
-  // BMON Hitfinder
-  if (Opts().Has(fles::Subsystem::BMON) && Opts().Has(Step::LocalReco)) {
-    auto calibSetup = yaml::ReadFromFile<bmon::CalibrateSetup>(opts.ParamsDir() / parFiles.bmon.calibrate);
-    fBmonCalibrator = std::make_unique<bmon::Calibrate>(calibSetup);
-
-    auto hitfindSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder);
-    fBmonHitFinder    = std::make_unique<bmon::Hitfind>(hitfindSetup);
-
-    if (fQaManager != nullptr && Opts().Has(QaStep::RecoBmon)) {
-      fBmonHitFinderQa = std::make_unique<bmon::HitfindQa>(fQaManager, "BmonHitfindEvent");
-      fBmonHitFinderQa->InitParameters(calibSetup, hitfindSetup);
-      fBmonHitFinderQa->Init();
-    }
-  }
-
   // TOF Hitfinder
   if (Opts().Has(fles::Subsystem::TOF) && Opts().Has(Step::LocalReco)) {
     auto calibSetup = yaml::ReadFromFile<tof::CalibrateSetup>(opts.ParamsDir() / parFiles.tof.calibrate);
@@ -249,6 +234,7 @@ void Reco::Init(const Options& opts)
     fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::STS);
     fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TRD);
     fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::TOF);
+    fbReconstructDigiEvents &= Opts().Has(fles::Subsystem::BMON);
   }
 
   // Tracking
@@ -267,6 +253,18 @@ void Reco::Init(const Options& opts)
   if (fbReconstructDigiEvents) {
     fEvSelectingMonitor.Reset();
 
+    // BMON hit finding in event reconstruction
+    auto bmonCalSetup = yaml::ReadFromFile<bmon::CalibrateSetup>(opts.ParamsDir() / parFiles.bmon.calibrate);
+    auto bmonHitSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder);
+    fBmonCalibrator   = std::make_unique<bmon::Calibrate>(bmonCalSetup);
+    fBmonHitFinder    = std::make_unique<bmon::Hitfind>(bmonHitSetup);
+    if (fQaManager != nullptr && Opts().Has(QaStep::RecoBmon)) {
+      fBmonHitFinderQa = std::make_unique<bmon::HitfindQa>(fQaManager, "BmonHitfindEvent");
+      fBmonHitFinderQa->InitParameters(bmonCalSetup, bmonHitSetup);
+      fBmonHitFinderQa->Init();
+    }
+
+    // Tracking in event reconstruction
     if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
       fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent, fQaManager, "CaEvent");
     }
@@ -524,6 +522,19 @@ void Reco::PrintTimings(xpu::timings& timings)
 bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
 {
   RecoResults recoEvent;
+  //* BMON hit reconstruction
+  {
+    auto [calDigis, calMonitor]          = (*fBmonCalibrator)(recoEvent.fBmon);
+    auto [hits, hitMonitor, digiIndices] = (*fBmonHitFinder)(calDigis);
+    if (fBmonHitFinderQa != nullptr) {
+      fBmonHitFinderQa->RegisterDigis(&calDigis);
+      fBmonHitFinderQa->RegisterHits(&hits);
+      fBmonHitFinderQa->RegisterDigiIndices(&digiIndices);
+      fBmonHitFinderQa->Exec();
+    }
+    recoEvent.bmonHits = std::move(hits);
+  }
+
   //* STS hit reconstruction
   {
     fEvSelectingMonitor.StartTimer(evselect::ETimer::StsHitFinder);
diff --git a/algo/global/RecoResults.h b/algo/global/RecoResults.h
index 670927953..25a3f73a2 100644
--- a/algo/global/RecoResults.h
+++ b/algo/global/RecoResults.h
@@ -12,6 +12,7 @@
 #include "DigiData.h"
 #include "PartitionedSpan.h"
 #include "PartitionedVector.h"
+#include "bmon/Hit.h"
 #include "ca/core/data/CaTrack.h"
 #include "ca/core/utils/CaVector.h"
 #include "sts/Cluster.h"
@@ -41,6 +42,7 @@ namespace cbm::algo
     PartitionedSpan<sts::Hit> stsHits;
     PartitionedVector<tof::Hit> tofHits;
     PartitionedVector<trd::Hit> trdHits;
+    PartitionedVector<bmon::Hit> bmonHits;
 
     ca::Vector<ca::Track> tracks;
     ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
index 2be7ddf5d..c328750aa 100644
--- a/algo/kfp/KfpV0FinderChain.h
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -59,5 +59,6 @@ namespace cbm::algo
    private:
     kfp::V0Finder fFinder;             ///< Instance of the V0-finding algorithm
     kfp::V0FinderMonitor fMonitorRun;  ///< Monitor per run
+    uint32_t fBmonAddress;             ///< Address of selected BMON
   };
 }  // namespace cbm::algo
-- 
GitLab


From 25b3a198ccc23026bfacc03d26454bbe6229b54a Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Tue, 11 Feb 2025 16:52:06 +0100
Subject: [PATCH 10/17] linking KFParticle library to statndalone cbmreco

- using a KFParticle version with no ROOT dependencies
---
 algo/ca/TrackingChain.cxx                  |  2 +
 algo/ca/core/utils/CaEnumArray.h           |  2 +-
 algo/ca/qa/CaQa.cxx                        | 18 +++---
 algo/detectors/bmon/Hitfind.h              |  5 +-
 algo/detectors/tof/Clusterizer.cxx         | 22 ++++++-
 algo/detectors/tof/Hitfind.cxx             |  1 -
 algo/evselector/KfpfV0FinderConfig.h       | 74 ----------------------
 algo/evselector/RecoEventSelectorMonitor.h | 14 ++--
 algo/global/ParFiles.cxx                   |  6 +-
 algo/global/Reco.cxx                       |  7 +-
 algo/global/RecoResults.h                  |  6 +-
 algo/kfp/KfpV0Finder.cxx                   | 70 +++++++++++++++++++-
 algo/kfp/KfpV0Finder.h                     | 44 ++++++++++++-
 algo/kfp/KfpV0FinderChain.cxx              | 19 ++++++
 algo/kfp/KfpV0FinderChain.h                | 12 ++--
 algo/kfp/KfpV0FinderConfig.h               |  2 +
 algo/kfp/interface/CMakeLists.txt          | 63 ++++++++++--------
 algo/kfp/interface/RootTypesDef.h          | 18 ++++++
 algo/qa/QaData.cxx                         |  1 +
 core/data/CbmEventTriggers.h               |  2 +-
 core/data/tof/CbmTofAddress.cxx            |  1 -
 external/CMakeLists.txt                    | 15 +++++
 macro/KF/configs/mcbm_kfpf_lambda.yaml     | 10 +--
 23 files changed, 273 insertions(+), 141 deletions(-)
 delete mode 100644 algo/evselector/KfpfV0FinderConfig.h
 create mode 100644 algo/kfp/interface/RootTypesDef.h

diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index 5ce7a4e51..374c23bb1 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -133,6 +133,7 @@ void TrackingChain::Init()
     fQa.RegisterParameters(&fCaFramework.GetParameters());
     fQa.Init();
   }
+  L_(info) << "TRACKING QA: " << fQa.IsActive();
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -225,6 +226,7 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
       int iHitInternal = fCaFramework.GetInputData().GetHit(fCaFramework.fRecoHits[trackFirstHit + iHit]).Id();
       const auto [detID, iPartition, iPartHit] = faHitExternalIndices[iHitInternal];
       switch (detID) {
+        // FIXME: store a global hit index instead of (partition, hit)
         case ca::EDetectorID::kSts: output.stsHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
         case ca::EDetectorID::kTof: output.tofHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
         case ca::EDetectorID::kTrd: output.trdHitIndices[iTrk].push_back(std::make_pair(iPartition, iPartHit)); break;
diff --git a/algo/ca/core/utils/CaEnumArray.h b/algo/ca/core/utils/CaEnumArray.h
index 5acd38589..84e90a0ed 100644
--- a/algo/ca/core/utils/CaEnumArray.h
+++ b/algo/ca/core/utils/CaEnumArray.h
@@ -38,7 +38,7 @@ namespace cbm::algo::ca
 
    public:
     /// \brief Mutable access operator, indexed by enum entry
-    T& operator[](const E& entry)
+    T& operator[](const E entry)
     {
       return std::array<T, static_cast<std::size_t>(E::END)>::operator[](static_cast<U>(entry));
     }
diff --git a/algo/ca/qa/CaQa.cxx b/algo/ca/qa/CaQa.cxx
index 05815198d..8d081862c 100644
--- a/algo/ca/qa/CaQa.cxx
+++ b/algo/ca/qa/CaQa.cxx
@@ -187,22 +187,22 @@ void Qa::Init()
       continue;
     }
     {
-      auto sName      = format("track_{}_theta", vsPointName[i]);
+      auto sName      = format("{}_track_{}_theta", GetTaskName(), vsPointName[i]);
       auto sTitl      = format("#theta at {} hit; #theta", vsPointName[i]);
       fvphTrkTheta[i] = MakeObj<H1D>(sName, sTitl, 62, 0., 90.);
     }
     {
-      auto sName    = format("track_{}_phi", vsPointName[i]);
+      auto sName    = format("{}_track_{}_phi", GetTaskName(), vsPointName[i]);
       auto sTitl    = format("#phi at {} hit; #phi", vsPointName[i]);
       fvphTrkPhi[i] = MakeObj<H1D>(sName, sTitl, 62, -180., 180.);
     }
     {
-      auto sName         = format("track_{}_thata_phi", vsPointName[i]);
+      auto sName         = format("{}_track_{}_thata_phi", GetTaskName(), vsPointName[i]);
       auto sTitl         = format("#theta vs #phi at {} hit; #phi; #theta", vsPointName[i]);
       fvphTrkPhiTheta[i] = MakeObj<H2D>(sName, sTitl, 62, -180., 180., 62, 0., 90.);
     }
     {
-      auto sName        = format("track_{}_chi2_ndf", vsPointName[i]);
+      auto sName        = format("{}_track_{}_chi2_ndf", GetTaskName(), vsPointName[i]);
       auto sTitl        = format("#chi^{{2}}/NDF at {} hit; #chi^{{2}}/NDF", vsPointName[i]);
       fvphTrkChi2Ndf[i] = MakeObj<H1D>(sName, sTitl, 100, 0., 20.);
     }
@@ -212,7 +212,7 @@ void Qa::Init()
     double xMin = -0.5;
     double xMax = double(knStaMax) - 0.5;
     {
-      auto sName      = "track_fst_lst_sta";
+      auto sName      = format("{}_track_fst_lst_sta", GetTaskName());
       auto sTitl      = "First vs. last station index;ID^{last}_{station};ID^{first}_{station}";
       fphTrkFstLstSta = MakeObj<H2D>(sName, sTitl, nBins, xMin, xMax, nBins, xMin, xMax);
     }
@@ -227,7 +227,7 @@ void Qa::Init()
         auto setNm = EHitSet::Input == hitSet ? "input" : "used";
         auto setTl = EHitSet::Input == hitSet ? "Input" : "Used";
         {  // XY
-          auto name = format("ca_hit_{}_occupancy_xy", setNm);
+          auto name = format("{}_ca_hit_{}_occupancy_xy", GetTaskName(), setNm);
           auto titl = format("{} hit occupancy in different stations in XY plane", setNm);
           auto canv = CanvasConfig(name, titl);
           for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -238,7 +238,7 @@ void Qa::Init()
           AddCanvasConfig(canv);
         }
         {  // ZX and ZY
-          auto name = format("ca_hit_{}_occupancy_zx_zy", setNm);
+          auto name = format("{}_ca_hit_{}_occupancy_zx_zy", GetTaskName(), setNm);
           auto titl = format("{} hit occupancy in different stations in ZX and ZY planes", setTl);
           auto canv = CanvasConfig(name, titl);
           {  // ZX
@@ -259,7 +259,7 @@ void Qa::Init()
         }
       }
       if (kDebug) {
-        auto name = format("ca_hit_usage_xy");
+        auto name = format("{}_ca_hit_usage_xy", GetTaskName());
         auto titl = format("Hit usage in different stations in XY plane");
         auto canv = CanvasConfig(name, titl);
         for (int iSt = 0; iSt < nSt; ++iSt) {
@@ -273,7 +273,7 @@ void Qa::Init()
 
     // Tracks canvas
     {
-      auto canv = CanvasConfig("ca_tracks", "Tracking output QA");
+      auto canv = CanvasConfig(format("{}_ca_tracks", GetTaskName()), "Tracking output QA");
       {
         auto pad = PadConfig(true, true, false, false, true);
         pad.RegisterHistogram(fvphTrkPhiTheta[0], "colz");
diff --git a/algo/detectors/bmon/Hitfind.h b/algo/detectors/bmon/Hitfind.h
index 68d7d3a9b..0257a9b74 100644
--- a/algo/detectors/bmon/Hitfind.h
+++ b/algo/detectors/bmon/Hitfind.h
@@ -45,11 +45,14 @@ namespace cbm::algo::bmon
     /// \param iThread  Index of thread
     Output_t operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t iThread = 0);
 
-   private:  // members
     /// \brief Returns an index of the diamond by the address
     /// \param address  A hardware address of the digi
     size_t GetDiamondIndex(uint32_t address) const { return ((fSelectionBitmask & address) >> fSelectionBitsOffset); }
 
+    /// \brief  Gets diamond addresses vector
+    const PODVector<uint32_t>& GetDiamondAddresses() const { return fDiamondAddress; }
+
+   private:                         // members
     uint32_t fNofThreads;           ///< Number of threads
     uint32_t fSelectionBitsOffset;  ///< Number of bits to ther right from the first bit in the selection mask
     uint32_t fSelectionBitmask;     ///< Selection bitmask
diff --git a/algo/detectors/tof/Clusterizer.cxx b/algo/detectors/tof/Clusterizer.cxx
index caf6d34f7..1a182b625 100644
--- a/algo/detectors/tof/Clusterizer.cxx
+++ b/algo/detectors/tof/Clusterizer.cxx
@@ -12,6 +12,9 @@
 #include <iomanip>
 #include <iostream>
 #include <map>
+#include <numeric>
+
+#include <fmt/format.h>
 
 namespace cbm::algo::tof
 {
@@ -49,6 +52,8 @@ namespace cbm::algo::tof
   //Iterator-based version. Faster than index-based version.
   Clusterizer::resultType Clusterizer::buildClusters(std::vector<inputType>& input)
   {
+    size_t nInputDigis{
+      std::accumulate(input.begin(), input.end(), 0, [](size_t s, const auto& v) { return s + v.size(); })};
     // Hit variables
     Hit cluster;
 
@@ -72,6 +77,10 @@ namespace cbm::algo::tof
       lastChanPos.push_back(input[chan].begin());
     }
 
+    size_t nInputDigisUsed{0};
+    if (nInputDigis > 0) {
+      //std::cout << "------------- call: buildClusters: number of digis: " << nInputDigis << '\n';
+    }
     for (int32_t chan = 0; (size_t) chan < numChan; chan++) {
 
       // Set partition vectors
@@ -83,11 +92,18 @@ namespace cbm::algo::tof
         chanSizes.back() = 0;
         continue;
       }
+      nInputDigisUsed += input[chan].size();
       inputType& storDigi = input[chan];
       auto digiIt         = storDigi.begin();
 
+      if (std::distance(storDigi.begin(), storDigi.end())) {
+        //std::cout << "DISTANCE: " << std::distance(storDigi.begin(), storDigi.end()) << '\n';
+        for (const auto& digi : storDigi) {
+          std::string sAddress = fmt::format("{:#08x}", digi.first->GetAddress());
+          //std::cout << "   " << digi.first->GetTime() << "ns, address: " << sAddress  << '\n';
+        }
+      }
       while (1 < std::distance(digiIt, storDigi.end())) {
-
         while (digiIt->first->GetSide() == std::next(digiIt)->first->GetSide()) {  // Not one Digi of each end!
           digiIt++;
           if (2 > std::distance(digiIt, storDigi.end())) {
@@ -181,6 +197,10 @@ namespace cbm::algo::tof
       storDigi.clear();
     }  // for( int32_t chan = 0; chan < iNbCh; chan++ )
 
+    //if (nInputDigis> 0) {
+    //  std::cout << ">>>>>> " << nInputDigisUsed << "/" << nInputDigis << '\n';
+    //}
+
     // Now check if another hit/cluster is started
     // and save it if it's the case.
     if (0 < cluster.numChan()) {
diff --git a/algo/detectors/tof/Hitfind.cxx b/algo/detectors/tof/Hitfind.cxx
index 119db83b1..5abf282f3 100644
--- a/algo/detectors/tof/Hitfind.cxx
+++ b/algo/detectors/tof/Hitfind.cxx
@@ -28,7 +28,6 @@ namespace cbm::algo::tof
 
       for (int32_t Sm = 0; Sm < NbSm; Sm++) {
         for (int32_t Rpc = 0; Rpc < NbRpc; Rpc++) {
-
           auto par = std::make_unique<cbm::algo::tof::ClusterizerRpcPar>();
 
           HitfindSetup::Rpc rpcPar = setup.rpcs[SmType][Sm * NbRpc + Rpc];
diff --git a/algo/evselector/KfpfV0FinderConfig.h b/algo/evselector/KfpfV0FinderConfig.h
deleted file mode 100644
index cbeaec0a7..000000000
--- a/algo/evselector/KfpfV0FinderConfig.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Sergei Zharko [committer] */
-
-/// \file   KfpfV0FinderConfig.h
-/// \date   29.01.2025
-/// \brief  Configuration structure for V0 selector in mCBM
-/// \author Sergei Zharko <s.zharko@gsi.de>
-
-#pragma once
-
-#include "yaml/Yaml.h"
-
-#include <vector>
-
-namespace cbm::algo::kfpf
-{
-  /// \struct CutsKfpf
-  /// \brief  Cuts for the KFParticleFinder
-  struct CutsKfpf {
-    float minDecayLength;  ///< Minimal decay length of particles [cm]
-    float minDecayLDL;     ///< Minimal value of decay length to decay length error ratio
-    float maxChi2NdfPrim;  ///< Maximal chi2/NDF for primary particles (coming from the PV)
-    float maxChi2NdfGeo;   ///< Maximal chi2/NDF for V0 candidates
-
-    CBM_YAML_PROPERTIES(
-      yaml::Property(&CutsKfpf::minDecayLength, "min_decay_length", "Minimal decay length of particles [cm]"),
-      yaml::Property(&CutsKfpf::minDecayLDL, "min_decay_ldl", "Minimal value of decay length to decay length error ratio"),
-      yaml::Property(&CutsKfpf::maxChi2NdfPrim, "max_chi2_ndf_prim", "Maximal chi2/NDF for primary particles"),
-      yaml::Property(&CutsKfpf::maxChi2NdfGeo, "max_chi2_ndf_geo", "Maximal chi2/NDF for V0 candidates"));
-  };
-
-  /// \struct ParticlePid
-  /// \brief  PID and pre-selection cuts for a given particle
-  struct ParticlePid {
-    int pdg;             ///< PDG code for particle
-    double minDca;       ///< Minimal DCA to PV [cm]
-    double minVelocity;  ///< Minimal velocity [cm/ns]
-    double maxVelocity;  ///< Maximal velocity [cm/ns]
-
-    CBM_YAML_PROPERTIES(
-      yaml::Property(&ParticlePid::pdg, "pdg", "PDG code of the particle"),
-      yaml::Property(&ParticlePid::minDca, "min_dca", "Minimal DCA to PV [cm]"),
-      yaml::Property(&ParticlePid::minVelocity, "min_velocity", "Minimal velocity [cm/ns]"),
-      yaml::Property(&ParticlePid::maxVelocity, "max_velocity", "Maximal velocity [cm/ns]"));
-  };
-
-  /// \struct Cuts;
-  struct Cuts {
-    CutsKfpf kfpf;                       ///< KFParticleFinder specific cuts
-    std::vector<ParticlePid> particles;  ///< Daughter PID cuts and other properties
-
-    CBM_YAML_PROPERTIES(
-      yaml::Property(&Cuts::kfpf, "kfpf", "Specific cuts for the KFParticleFinder"),
-      yaml::Property(&Cuts::particles,  "particles", "Particle identification cuts and properties"));
-  };
-
-  /// \struct LambdaFinderConfig
-  /// \brief  Configuration for the V0 finder
-  struct V0FinderConfig {
-    Cuts cuts;                     ///< Different selection cuts
-    double tZeroOffset;            ///< Offset for T0 [ns]
-    double qpAssignedUncertainty;  ///< Assigned relative uncertainty for q/p estimation
-    int primaryAssignedPdg;        ///< Assigned PDG hypothesis for primary particles
-    int reconstructPdg;            ///< PDG of the particle, the decay of which is to be reconstructed
-
-    CBM_YAML_PROPERTIES(
-      yaml::Property(&V0FinderConfig::cuts, "cuts", "Different selection cuts"),
-      yaml::Property(&V0FinderConfig::tZeroOffset, "t0_offset", "The t0 offset [ns]"),
-      yaml::Property(&V0FinderConfig::qpAssignedUncertainty, "qa_uncertainty", "Assigned relative uncertainty for q/p"),
-      yaml::Property(&V0FinderConfig::primaryAssignedPdg, "primary_pdg", "Assigned PDG code for primary tracks"),
-      yaml::Property(&V0FinderConfig::reconstructPdg, "reconstruct_pdg", "PDG code of the particle to be reconstructed"));
-  };
-}  // namespace cbm::algo::kfpf
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
index 41bc2ff13..82c68535c 100644
--- a/algo/evselector/RecoEventSelectorMonitor.h
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -19,11 +19,12 @@ namespace cbm::algo::evselect
   /// \brief Counter keys for the event selector monitor
   enum class ECounter
   {
-    EventsTotal,      ///< Total number of events processed
-    EventsNeStsHits,  ///< Events with not enough STS hits
-    EventsNeTofHits,  ///< Events with enough STS hits, but not enough TOF hits
-    EventsNeTracks,   ///< Events with enough hits, but not enough tracks
-    EventsSelected,   ///< Number of selected events
+    EventsTotal,       ///< Total number of events processed
+    EventsNeStsHits,   ///< Events with not enough STS hits
+    EventsNeTofHits,   ///< Events with enough STS hits, but not enough TOF hits
+    EventsNeBmonHits,  ///< Events with not enough BMon hits
+    EventsNeTracks,    ///< Events with enough hits, but not enough tracks
+    EventsSelected,    ///< Number of selected events
     END
   };
 
@@ -32,6 +33,7 @@ namespace cbm::algo::evselect
   /* clang-format off */
   enum class ETimer {
     EventReconstruction,
+      BmonHitFinder,
       StsHitFinder,
       TofHitFinder,
       TrdHitFinder,
@@ -53,10 +55,12 @@ namespace cbm::algo::evselect
       SetCounterName(ECounter::EventsTotal, "total events");
       SetCounterName(ECounter::EventsNeStsHits, "events discarded by N STS hits");
       SetCounterName(ECounter::EventsNeTofHits, "events discarded by N TOF hits");
+      SetCounterName(ECounter::EventsNeBmonHits, "events discarded by N BMon hits");
       SetCounterName(ECounter::EventsNeTracks, "events discarded by N tracks");
       SetCounterName(ECounter::EventsSelected, "selected events");
 
       SetTimerName(ETimer::EventReconstruction, "event reconstruction");
+      SetTimerName(ETimer::BmonHitFinder, "hit finding in Bmon");
       SetTimerName(ETimer::StsHitFinder, "hit finding in STS");
       SetTimerName(ETimer::TofHitFinder, "hit finding in TOF");
       SetTimerName(ETimer::TrdHitFinder, "hit finding in TRD");
diff --git a/algo/global/ParFiles.cxx b/algo/global/ParFiles.cxx
index 38773a0e8..7b73fe779 100644
--- a/algo/global/ParFiles.cxx
+++ b/algo/global/ParFiles.cxx
@@ -46,7 +46,7 @@ ParFiles::ParFiles(uint32_t runId)
 
       ca.mainConfig = "TrackingChainConfig_mcbm2022.yaml";
 
-      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
+      kfp.V0FinderConfig = "kfp_lambda_v22a.yaml";
       break;
 
     case Setup::mCBM2024_03:
@@ -71,7 +71,7 @@ ParFiles::ParFiles(uint32_t runId)
 
       ca.mainConfig = "TrackingChainConfig_mcbm2024.yaml";
 
-      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
+      kfp.V0FinderConfig = "kfp_lambda_v24a.yaml";
       break;
 
     case Setup::mCBM2024_05:
@@ -96,7 +96,7 @@ ParFiles::ParFiles(uint32_t runId)
 
       ca.mainConfig = "mcbm2024_05/TrackingChainConfig.yaml";
 
-      kfp.V0FinderConfig = "mcbm_kfp_lambda.yaml";
+      kfp.V0FinderConfig = "kfp_lambda_v24b.yaml";
       break;
 
     case Setup::mCBM2025_02:
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 0f933400d..37c37b508 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -266,7 +266,7 @@ void Reco::Init(const Options& opts)
 
     // Tracking in event reconstruction
     if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
-      fTracking = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent, fQaManager, "CaEvent");
+      fTrackingEvent = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent, fQaManager, "CaEvent");
     }
     else {
       fTrackingEvent = std::make_unique<TrackingChain>(ECbmRecoMode::EventByEvent);
@@ -277,6 +277,7 @@ void Reco::Init(const Options& opts)
 
     fV0Finder = std::make_unique<V0FinderChain>();
     fV0Finder->SetContext(&fContext);
+    fV0Finder->SetBmonDefinedAddresses(fBmonHitFinder->GetDiamondAddresses());
     fV0Finder->Init();
   }
 
@@ -524,8 +525,10 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
   RecoResults recoEvent;
   //* BMON hit reconstruction
   {
-    auto [calDigis, calMonitor]          = (*fBmonCalibrator)(recoEvent.fBmon);
+    fEvSelectingMonitor.StartTimer(evselect::ETimer::BmonHitFinder);
+    auto [calDigis, calMonitor]          = (*fBmonCalibrator)(digiEvent.fBmon);
     auto [hits, hitMonitor, digiIndices] = (*fBmonHitFinder)(calDigis);
+    fEvSelectingMonitor.StopTimer(evselect::ETimer::BmonHitFinder);
     if (fBmonHitFinderQa != nullptr) {
       fBmonHitFinderQa->RegisterDigis(&calDigis);
       fBmonHitFinderQa->RegisterHits(&hits);
diff --git a/algo/global/RecoResults.h b/algo/global/RecoResults.h
index 25a3f73a2..7795dda39 100644
--- a/algo/global/RecoResults.h
+++ b/algo/global/RecoResults.h
@@ -45,8 +45,8 @@ namespace cbm::algo
     PartitionedVector<bmon::Hit> bmonHits;
 
     ca::Vector<ca::Track> tracks;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTofHitIndices;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTrdHitIndices;
+    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTofHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTrdHitIndices;  // [trk][hit][(iPart, iHit)]
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index 5677dc5f6..5a5ddbc6e 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -9,19 +9,87 @@
 
 #include "kfp/KfpV0Finder.h"
 
+#include "global/RecoResults.h"
+
+#include <algorithm>
+#include <limits>
+#include <sstream>
+
+using cbm::algo::RecoResults;
 using cbm::algo::kfp::V0Finder;
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::vector<double> V0Finder::CollectDca(const RecoResults& recoEvent) const
+{
+  std::vector<double> dca(recoEvent.trackStsHitIndices.size(), std::numeric_limits<double>::signaling_NaN());
+  const auto& stsHitIndices = recoEvent.trackStsHitIndices;
+  for (size_t iTrk = 0; iTrk < stsHitIndices.size(); ++iTrk) {
+    const auto& stsHitIndicesInTrack = stsHitIndices[iTrk];
+    if (stsHitIndicesInTrack.size() < 2) {  // less then two sts hits
+      continue;
+    }
+    auto [iPtFst, iHitFst] = stsHitIndicesInTrack[0];
+    auto [iPtSnd, iHitSnd] = stsHitIndicesInTrack[1];
+    dca[iTrk]              = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
+  }
+  return dca;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+std::vector<double> V0Finder::CollectT0(gsl::span<const bmon::Hit> bmonHits) const
+{
+  std::vector<double> t0;
+  t0.reserve(bmonHits.size());
+  std::transform(bmonHits.begin(), bmonHits.end(), std::back_inserter(t0), [&](const auto& h) { return h.GetTime(); });
+  return t0;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+double V0Finder::EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const
+{
+  double factor{(fst.Z() - fOrigin[2]) / (snd.Z() - fst.Z())};
+  double dcaX{fst.X() - fOrigin[0] - factor * (snd.X() - fst.X())};
+  double dcaY{fst.Y() - fOrigin[1] - factor * (snd.Y() - fst.Y())};
+  return std::sqrt(dcaX * dcaX + dcaY * dcaY);
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void V0Finder::Init() {}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-CbmEventTriggers V0Finder::Process(const RecoResults&)
+CbmEventTriggers V0Finder::Process(const RecoResults& recoEvent)
 {
   CbmEventTriggers res;
   fEventMonitor.Reset();
   fEventMonitor.StartTimer(ETimer::Event);
+  fEventMonitor.IncrementCounter(ECounter::EventsTotal);
+
+  // ----- Define T0
+  // So far we cannot preselect a hit from multiple ones, we will be using all of them iteratively to find lambdas
+  auto vT0 = CollectT0(recoEvent.bmonHits[fBmonPartitionIndex]);
+  if (vT0.empty()) {
+    fEventMonitor.IncrementCounter(ECounter::EventsWoTzero);
+    return res;
+  }
+
+  //L_(info) << "------- Event: ";
+
+  // ----- Estimate DCA of tracks
+  // If a track has less then two STS hits, and undefined DCA value is stored
+  std::vector<double> vDca = CollectDca(recoEvent);
+
+  // ***** DEBUG: BEGIN
+  //for (auto dca: vDca) {
+  //  L_(info) << "dca=" << dca;
+  //}
+  // ***** DEBUG: END
+
+
   fEventMonitor.StopTimer(ETimer::Event);
   return res;
 }
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index e34e543d2..ef8b1e22b 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -14,6 +14,7 @@
 #include "global/RecoResults.h"
 #include "kfp/KfpV0FinderMonitor.h"
 
+#include <limits>
 #include <memory>
 
 
@@ -60,7 +61,10 @@ namespace cbm::algo::kfp
     /// \brief  Processes a reconstructed data sample, returns a collection of fired triggers
     CbmEventTriggers Process(const RecoResults& recoEvent);
 
-    /// \brief  Sets minimal pion DCA to primary vertex
+    /// \brief  Sets an address of a reference BMON diamond
+    /// \param  iPartition  An index of the BMON hit partition: used to selected the hits from the particular config
+    void SetBmonPartitionIndex(int iPartition) { fBmonPartitionIndex = iPartition; }
+
     /// \param  dca  DCA [cm]
     void SetMinPionDca(double dca) { fMinPionDca = dca; }
 
@@ -68,6 +72,14 @@ namespace cbm::algo::kfp
     /// \param  dca  DCA [cm]
     void SetMinProtonDca(double dca) { fMinProtonDca = dca; }
 
+    /// \brief  Sets origin
+    /// \param  x  X-coordinate of the origin [cm]
+    /// \param  y  Y-coordinate of the origin [cm]
+    /// \param  z  Z-coordinate of the origin [cm]
+    // FIXME: for now origin is defined as the target center, later it can be changed
+    void SetOrigin(double x, double y, double z) { fOrigin = {x, y, z}; }
+
+    /// \brief  Sets minimal pion DCA to primary vertex
     /// \brief  Sets pion velocity range
     /// \param  vMin  Minimal velocity [cm/ns]
     /// \param  vMax  Maximal velocity [cm/ns]
@@ -116,13 +128,29 @@ namespace cbm::algo::kfp
     void SetLdLCut2D(float cut) { GetKFParticleFinder()->SetLdLCut2D(cut); }
 
    private:
+    /// \brief  Collects T0 values among the BMON hits
+    /// \param  bmonHits  A span of BMON hits
+    /// \return A vector of T0-s
+    ///
+    /// If multiple T0-s are found, the routine will run multiple times, until V0-candidates are found
+    std::vector<double> CollectT0(gsl::span<const bmon::Hit> bmonHits) const;
+
+    /// \brief  Collects a vector of DCA
+    /// \param  recoEvent  Instance of a reconstructed event
+    /// \return A vector of DCAs to origin
+    std::vector<double> CollectDca(const RecoResults& recoEvent) const;
+
+    /// \brief  Estimate DCA of a track to origin
+    /// \param  fst  first  STS hit
+    /// \param  snd  second STS hit
+    /// \return dca [cm]
+    double EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const;
+
     //* Physical constants
     static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
     static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
     static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
 
-    /// \brief An instance of the topology reconstructor
-    std::unique_ptr<KFParticleTopoReconstructor> fpTopoReconstructor{std::make_unique<KFParticleTopoReconstructor>()};
 
     V0FinderMonitorData_t fEventMonitor;  ///< Monitor data instance (per event)
 
@@ -136,5 +164,15 @@ namespace cbm::algo::kfp
     double fMaxBetaProton{1.};           ///< Maximal proton velocity (beta) [c]
     double fMinBetaPion{0.};             ///< Minimal proton velocity (beta) [c]
     double fMaxBetaPion{1.};             ///< Maximal proton velocity (beta) [c]
+
+
+    //* Run-time variables, provided by framework
+    int fBmonPartitionIndex{-1};                ///< Index of selected partition in BMON hit vector
+    std::array<double, 3> fOrigin{0., 0., 0.};  ///< Coordinates of origin [cm]
+
+    //* Auxilary variables
+
+    /// \brief An instance of the topology reconstructor
+    std::unique_ptr<KFParticleTopoReconstructor> fpTopoReconstructor{std::make_unique<KFParticleTopoReconstructor>()};
   };
 }  // namespace cbm::algo::kfp
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
index 621f4d0d8..f41a0597a 100644
--- a/algo/kfp/KfpV0FinderChain.cxx
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -70,10 +70,29 @@ try {
   const auto& pion{particles[iPion]};
   const auto& proton{particles[iProton]};
 
+  // ----- Define a BMON diamond
+  if (fBmonDefinedAddresses.empty()) {
+    throw std::runtime_error("kfp::V0FinderChain: BMON available addresses were not set");
+  }
+  int iBmonPartitionSelect = -1;
+  for (int iPart = 0; iPart < static_cast<int>(fBmonDefinedAddresses.size()); ++iPart) {
+    if (config.bmonAddress == fBmonDefinedAddresses[iPart]) {
+      iBmonPartitionSelect = iPart;
+      break;
+    }
+  }
+  if (iBmonPartitionSelect < 0) {
+    std::stringstream msg;
+    msg << "kfp::V0FinderChain: a reference BMON address ( " << std::hex << config.bmonAddress << std::dec
+        << " ) differs from ones, provided by hitfinder. Please check your configuration";
+    throw std::runtime_error(msg.str());
+  }
+
   // ----- Set the V0-finder properties
   // TODO: In future, there are will be a several instances of the V0Finder, each for a particular thread
   {
     //* Set particle PID properties
+    fFinder.SetBmonPartitionIndex(iBmonPartitionSelect);
     fFinder.SetMinPionDca(pion.minDca);
     fFinder.SetMinProtonDca(proton.minDca);
     fFinder.SetPionVelocityRange(pion.minVelocity, pion.maxVelocity);
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
index c328750aa..630fc02e6 100644
--- a/algo/kfp/KfpV0FinderChain.h
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -10,6 +10,7 @@
 #pragma once
 
 #include "CbmEventTriggers.h"
+#include "PODVector.h"
 #include "base/SubChain.h"
 #include "global/RecoResults.h"
 #include "kfp/KfpV0Finder.h"
@@ -49,16 +50,19 @@ namespace cbm::algo
     /// \brief Finalizes the instance (called in the end of the run)
     void Finalize();
 
+    /// \brief Sets BMON diamond addresses array
+    /// \note  The addresses must be taken from bmon::Hitfind::GetDiamondAddresses()
+    void SetBmonDefinedAddresses(const PODVector<uint32_t>& addresses) { fBmonDefinedAddresses = addresses; }
+
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
     /// \brief  Processes an event, returns a collection of fired triggers
     EventOutput ProcessEvent(const RecoResults& recoEvent);
 
-
    private:
-    kfp::V0Finder fFinder;             ///< Instance of the V0-finding algorithm
-    kfp::V0FinderMonitor fMonitorRun;  ///< Monitor per run
-    uint32_t fBmonAddress;             ///< Address of selected BMON
+    kfp::V0Finder fFinder;                      ///< Instance of the V0-finding algorithm
+    kfp::V0FinderMonitor fMonitorRun;           ///< Monitor per run
+    PODVector<uint32_t> fBmonDefinedAddresses;  ///< Available addresses of BMON
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0FinderConfig.h b/algo/kfp/KfpV0FinderConfig.h
index dacc3705d..4bd992e0c 100644
--- a/algo/kfp/KfpV0FinderConfig.h
+++ b/algo/kfp/KfpV0FinderConfig.h
@@ -69,6 +69,7 @@ namespace cbm::algo::kfp
   /// \brief  Configuration for the V0 finder
   struct V0FinderConfig {
     Cuts cuts;                     ///< Different selection cuts
+    uint32_t bmonAddress;  ///< Address of BMON diamond (if multiple alternative are present, only one must be selected)
     double tZeroOffset;            ///< Offset for T0 [ns]
     double qpAssignedUncertainty;  ///< Assigned relative uncertainty for q/p estimation
     int primaryAssignedPdg;        ///< Assigned PDG hypothesis for primary particles
@@ -76,6 +77,7 @@ namespace cbm::algo::kfp
 
     CBM_YAML_PROPERTIES(
       yaml::Property(&V0FinderConfig::cuts, "cuts", "Different selection cuts"),
+      yaml::Property(&V0FinderConfig::bmonAddress, "bmon_address", "Address of reference BMON diamond"),
       yaml::Property(&V0FinderConfig::tZeroOffset, "t0_offset", "The t0 offset [ns]"),
       yaml::Property(&V0FinderConfig::qpAssignedUncertainty, "qa_uncertainty", "Assigned relative uncertainty for q/p"),
       yaml::Property(&V0FinderConfig::primaryAssignedPdg, "primary_pdg", "Assigned PDG code for primary tracks"),
diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
index 8b037a70e..ce26ad980 100644
--- a/algo/kfp/interface/CMakeLists.txt
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -3,35 +3,19 @@
 #  2) CbmKFParticleOfflineInterface -- a specific interface for the offline reconstruction in CBM.
 # The both libraries contain only the KFParticle core and do not include KFParticleTest or KFParticlePerformance.
 
-set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
 
-set(SRCS
-  ${KFP_SOURCE_DIR}/KFParticle.cxx
-  ${KFP_SOURCE_DIR}/KFParticleBase.cxx
-  ${KFP_SOURCE_DIR}/KFParticleBaseSIMD.cxx
-  ${KFP_SOURCE_DIR}/KFParticleDatabase.cxx
-  ${KFP_SOURCE_DIR}/KFParticleFinder.cxx
-  ${KFP_SOURCE_DIR}/KFParticlePVReconstructor.cxx
-  ${KFP_SOURCE_DIR}/KFParticleSIMD.cxx
-  ${KFP_SOURCE_DIR}/KFParticleTopoReconstructor.cxx
-  ${KFP_SOURCE_DIR}/KFPEmcCluster.cxx
-  ${KFP_SOURCE_DIR}/KFPTrack.cxx
-  ${KFP_SOURCE_DIR}/KFPTrackVector.cxx
-  ${KFP_SOURCE_DIR}/KFPVertex.cxx
-  ${KFP_SOURCE_DIR}/KFVertex.cxx
-)
-
-### CbmKFParticleOnlineInterface
-add_library(CbmKFParticleOnlineInterface INTERFACE)
-target_include_directories(CbmKFParticleOnlineInterface INTERFACE)
-target_compile_definitions(CbmKFParticleOnlineInterface 
-  INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
-target_link_libraries(CbmKFParticleOnlineInterface 
-  INTERFACE ROOT::Core KFParticle)
-install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
 
 if(NOT CBM_ONLINE_STANDALONE)
-### CbmKFParticleOfflineInterface
+  ### CbmKFParticleOnlineInterface
+  add_library(CbmKFParticleOnlineInterface INTERFACE)
+  target_include_directories(CbmKFParticleOnlineInterface INTERFACE)
+  target_compile_definitions(CbmKFParticleOnlineInterface 
+    INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
+  target_link_libraries(CbmKFParticleOnlineInterface 
+    INTERFACE ROOT::Core KFParticle)
+  install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
+
+  ### CbmKFParticleOfflineInterface
   add_library(CbmKFParticleOfflineInterface INTERFACE)
   target_include_directories(CbmKFParticleOfflineInterface INTERFACE)
   target_compile_definitions(CbmKFParticleOfflineInterface 
@@ -39,4 +23,31 @@ if(NOT CBM_ONLINE_STANDALONE)
   target_link_libraries(CbmKFParticleOfflineInterface 
     INTERFACE ROOT::Core KFParticle)
   install(TARGETS CbmKFParticleOfflineInterface DESTINATION lib)
+else()
+  # Creating a replacement of the CbmKFParticleOnlineInterface library for a standalone mode
+  set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
+
+  set(SRCS
+    ${KFP_SOURCE_DIR}/KFParticle.cxx
+    ${KFP_SOURCE_DIR}/KFParticleBase.cxx
+    ${KFP_SOURCE_DIR}/KFParticleBaseSIMD.cxx
+    ${KFP_SOURCE_DIR}/KFParticleDatabase.cxx
+    ${KFP_SOURCE_DIR}/KFParticleFinder.cxx
+    ${KFP_SOURCE_DIR}/KFParticlePVReconstructor.cxx
+    ${KFP_SOURCE_DIR}/KFParticleSIMD.cxx
+    ${KFP_SOURCE_DIR}/KFParticleTopoReconstructor.cxx
+    ${KFP_SOURCE_DIR}/KFPEmcCluster.cxx
+    ${KFP_SOURCE_DIR}/KFPTrack.cxx
+    ${KFP_SOURCE_DIR}/KFPTrackVector.cxx
+    ${KFP_SOURCE_DIR}/KFPVertex.cxx
+    ${KFP_SOURCE_DIR}/KFVertex.cxx
+  )
+  
+  add_library(CbmKFParticleOnlineInterface SHARED ${SRCS})
+  target_include_directories(CbmKFParticleOnlineInterface PUBLIC ${KFP_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+  target_compile_definitions(CbmKFParticleOnlineInterface PUBLIC NonhomogeneousField CBM CBM_ONLINE)
+  target_link_libraries(CbmKFParticleOnlineInterface 
+                 PUBLIC Vc::Vc 
+                        ROOT::Core)
+  install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
 endif()
diff --git a/algo/kfp/interface/RootTypesDef.h b/algo/kfp/interface/RootTypesDef.h
new file mode 100644
index 000000000..e6eaa5500
--- /dev/null
+++ b/algo/kfp/interface/RootTypesDef.h
@@ -0,0 +1,18 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   RootTypesDef.h
+/// \date   11.02.2025
+/// \brief  A compatibility header for the KFParticle code
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#if __has_include(<RtypesCore.h>)
+#include <RtypesCore.h>
+#else
+using Bool_t  = bool;
+using Int_t   = int;
+using Float_t = float;
+#endif
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 520ed4e52..6c42b3a52 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -57,6 +57,7 @@ try {
               << " with inconsistent flags (see HistogramMetadata::CheckFlags for detailes)";
           throw std::runtime_error(msg.str());
         }
+        L_(info) << " - task: " << task.fsName << ", histogram: " << h.GetName();
         vHistCfgs.emplace_back(h.GetName() + "!" + h.GetMetadataString(), task.fsName);
       };
       fsTaskNames += fmt::format("{} ", task.fsName);
diff --git a/core/data/CbmEventTriggers.h b/core/data/CbmEventTriggers.h
index 365d286e5..841d86bd4 100644
--- a/core/data/CbmEventTriggers.h
+++ b/core/data/CbmEventTriggers.h
@@ -73,7 +73,7 @@ class CbmEventTriggers {
   std::string ToString() const;
 
  private:
-  Trigger_t fTriggers;
+  Trigger_t fTriggers{0};
 
 #if !defined(NO_ROOT) && !XPU_IS_HIP_CUDA
   ClassDefNV(CbmEventTriggers, 1);
diff --git a/core/data/tof/CbmTofAddress.cxx b/core/data/tof/CbmTofAddress.cxx
index 71554dd1a..29c157345 100644
--- a/core/data/tof/CbmTofAddress.cxx
+++ b/core/data/tof/CbmTofAddress.cxx
@@ -66,4 +66,3 @@ std::string CbmTofAddress::ToString(int32_t address)
   msg << ", RpcType=" << setw(2) << CbmTofAddress::GetRpcType(address);
   return msg.str();
 }
-
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index d08436836..9328a585c 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -70,6 +70,7 @@ if(DOWNLOAD_EXTERNALS)
   Include(InstallYamlCpp.cmake)
   Include(InstallPoolSTL.cmake)
 
+
   if (NOT CBM_ONLINE_STANDALONE) # Not required for online standalone
 
     Include(InstallKFParticle.cmake)
@@ -86,8 +87,22 @@ if(DOWNLOAD_EXTERNALS)
       Include(InstallQa.cmake)
     endif()
 
+  else()
+
+    # Download KFParticle source from the repository, but do not install it
+    download_project_if_needed(PROJECT kfparticle_source
+      GIT_REPOSITORY "https://github.com/szharko/KFParticle.git"
+      GIT_TAG "d088e3e019ac588a0528aa560ceaca247580d881" # CBM_ONLINE directive to turn off dependencies on ROOT
+      SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/KFParticle
+      CONFIGURE_COMMAND ""
+      BUILD_COMMAND ""
+      INSTALL_COMMAND ""
+    )
+  
   endif()
 
+  
+
 else()
   # Define targets which are needed by CbmRoot but are not available
   # whithout the external packages
diff --git a/macro/KF/configs/mcbm_kfpf_lambda.yaml b/macro/KF/configs/mcbm_kfpf_lambda.yaml
index 01e77b185..fc5599d37 100644
--- a/macro/KF/configs/mcbm_kfpf_lambda.yaml
+++ b/macro/KF/configs/mcbm_kfpf_lambda.yaml
@@ -7,11 +7,11 @@
 # \brief  Standard configuration for the lambda finding in mCBM
 # \author Sergei Zharko <s.zharko@gsi.de>
 
-reconstruct_pdg: 3122  # Lambda -> pi- p
-
-t0_offset: 0.12      # Time offset for T0 [ns]
-qa_uncertainty: 0.1  # Assigned relative uncertainty for the q/p estimation
-primary_pdg: 321     # Assigned PDG code for primary tracks
+reconstruct_pdg: 3122     # Lambda -> pi- p
+bmon_address: 0x00202806  # Address of reference BMON diamond
+t0_offset: 0.12           # Time offset for T0 [ns]
+qa_uncertainty: 0.1       # Assigned relative uncertainty for the q/p estimation
+primary_pdg: 321          # Assigned PDG code for primary tracks
 
 cuts:  # Different cuts
   kfp:  # KFParticleFinder specific cuts
-- 
GitLab


From a861698509c55f0961aca1ad151c07cab7d442e2 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Wed, 12 Feb 2025 00:51:57 +0100
Subject: [PATCH 11/17] kfp-online: linking ROOT::Hist and ROOT::MathCore,
 changed the dependency on CbmKFParticle<Online/Offline>Interface to PUBLIC

---
 algo/CMakeLists.txt               | 4 ++--
 algo/kfp/interface/CMakeLists.txt | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 6ba7a06c0..3b03adb99 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -227,7 +227,7 @@ target_link_libraries(Algo
             external::fles_monitoring
             cppzmq
             poolstl
-  PRIVATE   CbmKFParticleOnlineInterface 
+            CbmKFParticleOnlineInterface 
 )
 target_compile_definitions(Algo PUBLIC NO_ROOT)
 xpu_attach(Algo ${DEVICE_SRCS})
@@ -301,7 +301,7 @@ if (NOT CBM_ONLINE_STANDALONE)
               external::fles_monitoring
               cppzmq
               poolstl
-  PRIVATE   CbmKFParticleOfflineInterface 
+              CbmKFParticleOfflineInterface 
   )
   xpu_attach(AlgoOffline ${DEVICE_SRCS})
 
diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
index ce26ad980..c5c21f04a 100644
--- a/algo/kfp/interface/CMakeLists.txt
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -12,7 +12,7 @@ if(NOT CBM_ONLINE_STANDALONE)
   target_compile_definitions(CbmKFParticleOnlineInterface 
     INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
   target_link_libraries(CbmKFParticleOnlineInterface 
-    INTERFACE ROOT::Core KFParticle)
+    INTERFACE ROOT::Core ROOT::Hist ROOT::MathCore KFParticle)
   install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
 
   ### CbmKFParticleOfflineInterface
@@ -21,7 +21,7 @@ if(NOT CBM_ONLINE_STANDALONE)
   target_compile_definitions(CbmKFParticleOfflineInterface 
     INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
   target_link_libraries(CbmKFParticleOfflineInterface 
-    INTERFACE ROOT::Core KFParticle)
+    INTERFACE ROOT::Core ROOT::Hist ROOT::MathCore KFParticle)
   install(TARGETS CbmKFParticleOfflineInterface DESTINATION lib)
 else()
   # Creating a replacement of the CbmKFParticleOnlineInterface library for a standalone mode
-- 
GitLab


From 394b7e37ccff0a32a0165de18160c1aeb73511ea Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Wed, 12 Feb 2025 11:52:37 +0100
Subject: [PATCH 12/17] online: remove temporary debugging output

---
 algo/CMakeLists.txt                |  4 ++--
 algo/detectors/tof/Clusterizer.cxx | 21 --------------------
 algo/kfp/interface/CMakeLists.txt  | 32 +++++++++++++-----------------
 3 files changed, 16 insertions(+), 41 deletions(-)

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 3b03adb99..9e7f23db7 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -227,7 +227,7 @@ target_link_libraries(Algo
             external::fles_monitoring
             cppzmq
             poolstl
-            CbmKFParticleOnlineInterface 
+  PRIVATE   CbmKFParticleOnlineInterface 
 )
 target_compile_definitions(Algo PUBLIC NO_ROOT)
 xpu_attach(Algo ${DEVICE_SRCS})
@@ -301,7 +301,7 @@ if (NOT CBM_ONLINE_STANDALONE)
               external::fles_monitoring
               cppzmq
               poolstl
-              CbmKFParticleOfflineInterface 
+    PRIVATE   CbmKFParticleOnlineInterface 
   )
   xpu_attach(AlgoOffline ${DEVICE_SRCS})
 
diff --git a/algo/detectors/tof/Clusterizer.cxx b/algo/detectors/tof/Clusterizer.cxx
index 1a182b625..fb36f7d47 100644
--- a/algo/detectors/tof/Clusterizer.cxx
+++ b/algo/detectors/tof/Clusterizer.cxx
@@ -12,9 +12,6 @@
 #include <iomanip>
 #include <iostream>
 #include <map>
-#include <numeric>
-
-#include <fmt/format.h>
 
 namespace cbm::algo::tof
 {
@@ -52,8 +49,6 @@ namespace cbm::algo::tof
   //Iterator-based version. Faster than index-based version.
   Clusterizer::resultType Clusterizer::buildClusters(std::vector<inputType>& input)
   {
-    size_t nInputDigis{
-      std::accumulate(input.begin(), input.end(), 0, [](size_t s, const auto& v) { return s + v.size(); })};
     // Hit variables
     Hit cluster;
 
@@ -77,10 +72,6 @@ namespace cbm::algo::tof
       lastChanPos.push_back(input[chan].begin());
     }
 
-    size_t nInputDigisUsed{0};
-    if (nInputDigis > 0) {
-      //std::cout << "------------- call: buildClusters: number of digis: " << nInputDigis << '\n';
-    }
     for (int32_t chan = 0; (size_t) chan < numChan; chan++) {
 
       // Set partition vectors
@@ -92,17 +83,9 @@ namespace cbm::algo::tof
         chanSizes.back() = 0;
         continue;
       }
-      nInputDigisUsed += input[chan].size();
       inputType& storDigi = input[chan];
       auto digiIt         = storDigi.begin();
 
-      if (std::distance(storDigi.begin(), storDigi.end())) {
-        //std::cout << "DISTANCE: " << std::distance(storDigi.begin(), storDigi.end()) << '\n';
-        for (const auto& digi : storDigi) {
-          std::string sAddress = fmt::format("{:#08x}", digi.first->GetAddress());
-          //std::cout << "   " << digi.first->GetTime() << "ns, address: " << sAddress  << '\n';
-        }
-      }
       while (1 < std::distance(digiIt, storDigi.end())) {
         while (digiIt->first->GetSide() == std::next(digiIt)->first->GetSide()) {  // Not one Digi of each end!
           digiIt++;
@@ -197,10 +180,6 @@ namespace cbm::algo::tof
       storDigi.clear();
     }  // for( int32_t chan = 0; chan < iNbCh; chan++ )
 
-    //if (nInputDigis> 0) {
-    //  std::cout << ">>>>>> " << nInputDigisUsed << "/" << nInputDigis << '\n';
-    //}
-
     // Now check if another hit/cluster is started
     // and save it if it's the case.
     if (0 < cluster.numChan()) {
diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
index c5c21f04a..c1eaadf76 100644
--- a/algo/kfp/interface/CMakeLists.txt
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -1,10 +1,3 @@
-# The script creates two libraries:
-#  1) CbmKFParticleOnlineInterface -- a specific KFParticle library interface for the CBM online reconstruction;
-#  2) CbmKFParticleOfflineInterface -- a specific interface for the offline reconstruction in CBM.
-# The both libraries contain only the KFParticle core and do not include KFParticleTest or KFParticlePerformance.
-
-
-
 if(NOT CBM_ONLINE_STANDALONE)
   ### CbmKFParticleOnlineInterface
   add_library(CbmKFParticleOnlineInterface INTERFACE)
@@ -12,17 +5,19 @@ if(NOT CBM_ONLINE_STANDALONE)
   target_compile_definitions(CbmKFParticleOnlineInterface 
     INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
   target_link_libraries(CbmKFParticleOnlineInterface 
-    INTERFACE ROOT::Core ROOT::Hist ROOT::MathCore KFParticle)
+              INTERFACE KFParticle
+                        ROOT::Core 
+                        ROOT::Hist 
+                        ROOT::MathCore
+                        ROOT::Gpad
+                        ROOT::Graf
+                        ROOT::Physics
+                        ROOT::EG
+                        ROOT::RIO
+                        ROOT::Tree
+                        ROOT::MathCore
+                        )
   install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
-
-  ### CbmKFParticleOfflineInterface
-  add_library(CbmKFParticleOfflineInterface INTERFACE)
-  target_include_directories(CbmKFParticleOfflineInterface INTERFACE)
-  target_compile_definitions(CbmKFParticleOfflineInterface 
-    INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
-  target_link_libraries(CbmKFParticleOfflineInterface 
-    INTERFACE ROOT::Core ROOT::Hist ROOT::MathCore KFParticle)
-  install(TARGETS CbmKFParticleOfflineInterface DESTINATION lib)
 else()
   # Creating a replacement of the CbmKFParticleOnlineInterface library for a standalone mode
   set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
@@ -48,6 +43,7 @@ else()
   target_compile_definitions(CbmKFParticleOnlineInterface PUBLIC NonhomogeneousField CBM CBM_ONLINE)
   target_link_libraries(CbmKFParticleOnlineInterface 
                  PUBLIC Vc::Vc 
-                        ROOT::Core)
+                        ROOT::Core
+                        )
   install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
 endif()
-- 
GitLab


From aeeec7b9b1c779cb4703cba032db9f7fa00a0622 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 13 Feb 2025 03:39:52 +0100
Subject: [PATCH 13/17] online: V0Finder implementation

---
 algo/ca/core/data/CaTrack.h                |   8 +-
 algo/ca/core/utils/CaMonitorData.h         |   4 +
 algo/data/sts/Hit.h                        |  15 +-
 algo/detectors/tof/Hit.h                   |  15 +-
 algo/evselector/RecoEventSelectorMonitor.h |   2 +
 algo/global/Reco.cxx                       |   7 +-
 algo/global/RecoResults.h                  |   8 +-
 algo/kfp/KfpV0Finder.cxx                   | 398 +++++++++++++++++++--
 algo/kfp/KfpV0Finder.h                     | 123 ++++++-
 algo/kfp/KfpV0FinderConfig.cxx             |   6 +
 algo/kfp/KfpV0FinderMonitor.h              |  24 +-
 11 files changed, 540 insertions(+), 70 deletions(-)

diff --git a/algo/ca/core/data/CaTrack.h b/algo/ca/core/data/CaTrack.h
index 29b7f3859..cf4663610 100644
--- a/algo/ca/core/data/CaTrack.h
+++ b/algo/ca/core/data/CaTrack.h
@@ -27,6 +27,8 @@ namespace cbm::algo::ca
   ///
   class Track {
    public:
+    using TrackParam_t = cbm::algo::kf::TrackParamS;
+
     friend class boost::serialization::access;
 
     Track() = default;
@@ -43,9 +45,9 @@ namespace cbm::algo::ca
    public:
     int fNofHits{kfdefs::Undef<int>};  ///< Number of hits in track
 
-    cbm::algo::kf::TrackParamS fParFirst;  ///< Track parameters on the first station
-    cbm::algo::kf::TrackParamS fParLast;   ///< Track parameters on the last station
-    cbm::algo::kf::TrackParamS fParPV;     ///< Track parameters in the primary vertex
+    TrackParam_t fParFirst;  ///< Track parameters on the first station
+    TrackParam_t fParLast;   ///< Track parameters on the last station
+    TrackParam_t fParPV;     ///< Track parameters in the primary vertex
   };
 
 }  // namespace cbm::algo::ca
diff --git a/algo/ca/core/utils/CaMonitorData.h b/algo/ca/core/utils/CaMonitorData.h
index 09b3d0e11..5d93816c1 100644
--- a/algo/ca/core/utils/CaMonitorData.h
+++ b/algo/ca/core/utils/CaMonitorData.h
@@ -82,6 +82,10 @@ namespace cbm::algo::ca
     /// \brief Resets all the counters and timers
     void Reset();
 
+    /// \brief Resets a particular counter
+    /// \param key  Counter key
+    void ResetCounter(ECounterKey key) { faCounters[key] = 0; }
+
     /// \brief Starts timer
     /// \param key  Timer key
     void StartTimer(ETimerKey key) { faTimers[key].Start(); }
diff --git a/algo/data/sts/Hit.h b/algo/data/sts/Hit.h
index ab1a733f2..2df0d8370 100644
--- a/algo/data/sts/Hit.h
+++ b/algo/data/sts/Hit.h
@@ -26,14 +26,13 @@ namespace cbm::algo::sts
     u32 fBackClusterId;   ///< Index of back-side cluster, used by tracking to reduce combinatorics
 
     // Interface for tracking
-    real X() const { return fX; }
-    real Y() const { return fY; }
-    real Z() const { return fZ; }
-    u32 Time() const { return fTime; }
-
-    real Dx() const { return fDx; }
-    real Dy() const { return fDy; }
-    real TimeError() const { return fTimeError; }
+    double X() const { return fX; }
+    double Y() const { return fY; }
+    double Z() const { return fZ; }
+    double Time() const { return fTime; }
+    double Dx() const { return fDx; }
+    double Dy() const { return fDy; }
+    double TimeError() const { return fTimeError; }
 
    private:  // serialization
     friend class boost::serialization::access;
diff --git a/algo/detectors/tof/Hit.h b/algo/detectors/tof/Hit.h
index a7608fc72..8e3a1a068 100644
--- a/algo/detectors/tof/Hit.h
+++ b/algo/detectors/tof/Hit.h
@@ -53,14 +53,13 @@ namespace cbm::algo::tof
 
     // Interface for tracker
 
-    real X() const { return hitPos.X(); }
-    real Y() const { return hitPos.Y(); }
-    real Z() const { return hitPos.Z(); }
-    u32 Time() const { return hitTime; }
-
-    real Dx() const { return hitPosErr.X(); }
-    real Dy() const { return hitPosErr.Y(); }
-    real TimeError() const { return hitTimeErr; }
+    double X() const { return hitPos.X(); }
+    double Y() const { return hitPos.Y(); }
+    double Z() const { return hitPos.Z(); }
+    double Time() const { return hitTime; }
+    double Dx() const { return hitPosErr.X(); }
+    double Dy() const { return hitPosErr.Y(); }
+    double TimeError() const { return hitTimeErr; }
 
     // Interface end
 
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
index 82c68535c..de2eb4a27 100644
--- a/algo/evselector/RecoEventSelectorMonitor.h
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -38,6 +38,7 @@ namespace cbm::algo::evselect
       TofHitFinder,
       TrdHitFinder,
       TrackFinder,
+      V0Finder,
     END
   };
   /* clang-format on */
@@ -65,6 +66,7 @@ namespace cbm::algo::evselect
       SetTimerName(ETimer::TofHitFinder, "hit finding in TOF");
       SetTimerName(ETimer::TrdHitFinder, "hit finding in TRD");
       SetTimerName(ETimer::TrackFinder, "track finding");
+      SetTimerName(ETimer::V0Finder, "V0 finding");
     }
 
    private:
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 37c37b508..b2a735559 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -597,9 +597,10 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
   }
 
   //* V0-selector
-  {
-    auto triggers = fV0Finder->ProcessEvent(recoEvent);
-  }
+  fEvSelectingMonitor.StartTimer(evselect::ETimer::V0Finder);
+  auto triggers = fV0Finder->ProcessEvent(recoEvent);
+  fEvSelectingMonitor.StopTimer(evselect::ETimer::V0Finder);
+
 
   fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
   return true;
diff --git a/algo/global/RecoResults.h b/algo/global/RecoResults.h
index 7795dda39..3bcee8be6 100644
--- a/algo/global/RecoResults.h
+++ b/algo/global/RecoResults.h
@@ -27,6 +27,8 @@ namespace cbm::algo
   /// @name RecoResults
   /// @brief  Structure to keep reconstructed results: digi-events, hits and tracks
   struct RecoResults {
+    using HitId_t = std::pair<uint32_t, uint32_t>;  // Hit ID by track
+
     PODVector<CbmBmonDigi> bmonDigis;
     PODVector<CbmStsDigi> stsDigis;
     PODVector<CbmMuchDigi> muchDigis;
@@ -45,8 +47,8 @@ namespace cbm::algo
     PartitionedVector<bmon::Hit> bmonHits;
 
     ca::Vector<ca::Track> tracks;
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackStsHitIndices;  // [trk][hit][(iPart, iHit)]
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTofHitIndices;  // [trk][hit][(iPart, iHit)]
-    ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>> trackTrdHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<HitId_t>> trackStsHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<HitId_t>> trackTofHitIndices;  // [trk][hit][(iPart, iHit)]
+    ca::Vector<std::vector<HitId_t>> trackTrdHitIndices;  // [trk][hit][(iPart, iHit)]
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index 5a5ddbc6e..2488ba89e 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -18,32 +18,99 @@
 using cbm::algo::RecoResults;
 using cbm::algo::kfp::V0Finder;
 
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::AssignMomentum(const PartitionedVector<tof::Hit>& tofHits,
+                              const std::vector<RecoResults::HitId_t>& tofHitIds, double t0, ParticleInfo& particleInfo)
+{
+  if (tofHitIds.empty()) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWoTofHits);
+    return false;
+  }
+
+  double beta{0.};
+  if constexpr (kUseAverageSpeed) {
+    for (const auto& hitId : tofHitIds) {
+      beta += EstimateBeta(tofHits[hitId.first][hitId.second], t0);
+    }
+    beta /= tofHitIds.size();
+  }
+  else {
+    const auto& hitId = tofHitIds.back();
+    beta              = EstimateBeta(tofHits[hitId.first][hitId.second], t0);
+  }
+  if (beta < 0.) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWNegativeTofHitTime);
+    return false;
+  }
+  else if (beta > 1.) {
+    fEventMonitor.IncrementCounter(ECounter::TracksWUnphysicalBeta);
+    return false;
+  }
+  double gamma{1. / sqrt(1. - beta * beta)};
+  particleInfo.fBeta = beta;
+  particleInfo.fQp   = particleInfo.fQp / (gamma * beta * particleInfo.fMass);
+  return true;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::AssignPid(ParticleInfo& particleInfo)
+{
+  if (std::isnan(particleInfo.fDca)) {
+    particleInfo.fPdg = kUndefPdg;
+    fEventMonitor.IncrementCounter(ECounter::TracksWoPid);
+  }
+  else if (particleInfo.fDca > fMinPionDca) {
+    // pi-
+    particleInfo.fPdg    = -211;
+    particleInfo.fMass   = kPionMass;
+    particleInfo.fCharge = -1;
+    fEventMonitor.IncrementCounter(ECounter::PionsDca);
+  }
+  else if (particleInfo.fDca > fMinProtonDca) {
+    // proton
+    particleInfo.fPdg    = 2212;
+    particleInfo.fMass   = kProtonMass;
+    particleInfo.fCharge = 1;
+    fEventMonitor.IncrementCounter(ECounter::ProtonsDca);
+  }
+  else {
+    // primary
+    //particleInfo.fPdg = fPrimaryAssignedPdg;
+    particleInfo.fPdg = kUndefPdg;
+    fEventMonitor.IncrementCounter(ECounter::TracksWoPid);
+  }
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
-std::vector<double> V0Finder::CollectDca(const RecoResults& recoEvent) const
+void V0Finder::CollectDca(const RecoResults& recoEvent)
 {
-  std::vector<double> dca(recoEvent.trackStsHitIndices.size(), std::numeric_limits<double>::signaling_NaN());
   const auto& stsHitIndices = recoEvent.trackStsHitIndices;
   for (size_t iTrk = 0; iTrk < stsHitIndices.size(); ++iTrk) {
     const auto& stsHitIndicesInTrack = stsHitIndices[iTrk];
     if (stsHitIndicesInTrack.size() < 2) {  // less then two sts hits
+      fEventMonitor.IncrementCounter(ECounter::TracksWoStsHits);
       continue;
     }
-    auto [iPtFst, iHitFst] = stsHitIndicesInTrack[0];
-    auto [iPtSnd, iHitSnd] = stsHitIndicesInTrack[1];
-    dca[iTrk]              = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
+    auto& particleInfo        = fvParticleInfo[iTrk];
+    auto [iPtFst, iHitFst]    = stsHitIndicesInTrack[0];
+    auto [iPtSnd, iHitSnd]    = stsHitIndicesInTrack[1];
+    fvParticleInfo[iTrk].fDca = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
+    AssignPid(particleInfo);
   }
-  return dca;
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-std::vector<double> V0Finder::CollectT0(gsl::span<const bmon::Hit> bmonHits) const
+void V0Finder::CollectT0(gsl::span<const bmon::Hit> bmonHits)
 {
-  std::vector<double> t0;
-  t0.reserve(bmonHits.size());
-  std::transform(bmonHits.begin(), bmonHits.end(), std::back_inserter(t0), [&](const auto& h) { return h.GetTime(); });
-  return t0;
+  fvT0s.clear();
+  fvT0s.reserve(bmonHits.size());
+  std::transform(bmonHits.begin(), bmonHits.end(), std::back_inserter(fvT0s),
+                 [&](const auto& h) { return h.GetTime(); });
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -58,38 +125,317 @@ double V0Finder::EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void V0Finder::Init() {}
+double V0Finder::EstimateBeta(const tof::Hit& hit, double t0) const
+{
+  double t = hit.Time() - t0 - fTzeroOffset;
+  double x{hit.X() - fOrigin[0]};
+  double y{hit.Y() - fOrigin[1]};
+  double z{hit.Y() - fOrigin[2]};
+  double x2{x * x};
+  double y2{y * y};
+  double z2{z * z};
+  double r2{x2 + y2 + z2};
+  return std::sqrt(r2) / (t * kSpeedOfLight);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::FindV0Candidates(const RecoResults& recoEvent, double t0)
+{
+  const auto& tracks = recoEvent.tracks;
+
+  // Reset temporary data structures
+  InitTrackParamVectors(tracks);
+  fpTopoReconstructor->Clear();
+  fvSelectedTrackIds.clear();
+  fvSelectedTrackIds.reserve(tracks.size());
+  fEventMonitor.ResetCounter(ECounter::TracksWoMomentum);
+  fEventMonitor.ResetCounter(ECounter::TracksSelected);
+  fEventMonitor.ResetCounter(ECounter::Pions);
+  fEventMonitor.ResetCounter(ECounter::Protons);
+  fEventMonitor.ResetCounter(ECounter::EventsLambdaCand);
+
+  // Preselect tracks
+  uint32_t nProtonCandidates{0};
+  uint32_t nPionCandidates{0};
+  uint32_t nSelectedTracks{0};
+  fEventMonitor.StartTimer(ETimer::PreselectTracks);
+  for (size_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {  // Over all tracks
+    auto& particleInfo = fvParticleInfo[iTrk];
+
+    // Cut tracks with undefined dca (by PID == -2)
+    // NOTE: if fPdg == kUndefPdg, beta and QP were not estimated for this track on previous iterations as well
+    if (particleInfo.fPdg == kUndefPdg) {
+      continue;
+    }
+
+    // Reset fields of the ParticleInfo, which could be filled on the previous iteration
+    particleInfo.fBeta      = std::numeric_limits<double>::quiet_NaN();
+    particleInfo.fQp        = std::numeric_limits<double>::quiet_NaN();
+    particleInfo.fbSelected = false;
+
+    // Assign momentum to tracks
+    if (!AssignMomentum(recoEvent.tofHits, recoEvent.trackTofHitIndices[iTrk], t0, particleInfo)) {
+      fEventMonitor.IncrementCounter(ECounter::TracksWoMomentum);
+      continue;  // No momentum was assigned
+    }
+
+    // Select tracks
+    if (!SelectTrack(particleInfo)) {
+      continue;
+    }
+    fvSelectedTrackIds.push_back(iTrk);
+    particleInfo.fbSelected = true;
+
+    // Update track parameters
+    double qpVar{particleInfo.fQp * fQpAssignedUncertainty};
+    qpVar = qpVar * qpVar;
+    auto& trkParam{fvTrackParam[iTrk]};
+    trkParam.first.SetQp(particleInfo.fQp);
+    trkParam.first.SetC44(qpVar);
+    trkParam.second.SetQp(particleInfo.fQp);
+    trkParam.second.SetC44(qpVar);
+
+    switch (particleInfo.fPdg) {
+      case -211: ++nPionCandidates; break;
+      case 2212: ++nProtonCandidates; break;
+    }
+    ++nSelectedTracks;
+  }
+  fEventMonitor.StopTimer(ETimer::PreselectTracks);
+  if (!nPionCandidates || !nProtonCandidates) {
+    return false;  // no Lambda can be found
+  }
+  fEventMonitor.IncrementCounter(ECounter::EventsLambdaCand);
+  fEventMonitor.IncrementCounter(ECounter::TracksSelected, nSelectedTracks);
+  fEventMonitor.IncrementCounter(ECounter::Pions, nPionCandidates);
+  fEventMonitor.IncrementCounter(ECounter::Protons, nProtonCandidates);
+
+  // Initialize and run the KFParticleFinder
+  fEventMonitor.StartTimer(ETimer::InitKfp);
+  KFPTrackVector kfpTracksFst;
+  KFPTrackVector kfpTracksLst;
+  kfpTracksFst.Resize(nSelectedTracks);
+  kfpTracksLst.Resize(nSelectedTracks);
+  fpTopoReconstructor->Init(kfpTracksFst, kfpTracksLst);
+  for (uint32_t iKfpTrk = 0; iKfpTrk < fvSelectedTrackIds.size(); ++iKfpTrk) {  // Over selected tracks
+    uint32_t iCaTrk{fvSelectedTrackIds[iKfpTrk]};
+    const auto& trkParam{fvTrackParam[iCaTrk]};
+    const auto& particleInfo{fvParticleInfo[iCaTrk]};
+    SetKfpTrackParameters(kfpTracksFst, iKfpTrk, iCaTrk, trkParam.first, particleInfo);
+    SetKfpTrackParameters(kfpTracksLst, iKfpTrk, iCaTrk, trkParam.second, particleInfo);
+  }
+  fpTopoReconstructor->AddPV(MakeKfpPrimaryVertex(fOrigin));
+  fpTopoReconstructor->SortTracks();
+  fEventMonitor.StopTimer(ETimer::InitKfp);
+  fEventMonitor.StartTimer(ETimer::ExecKfp);
+  fpTopoReconstructor->ReconstructParticles();
+  fEventMonitor.StopTimer(ETimer::ExecKfp);
+  const auto& particles{fpTopoReconstructor->GetParticles()};
+
+  // Scan for Lambda-candidates
+  uint32_t nLambdaCandidates =
+    std::count_if(particles.begin(), particles.end(), [&](const auto& p) { return p.GetPDG() == 3122; });
+  fEventMonitor.IncrementCounter(ECounter::KfpLambdaCandidates, nLambdaCandidates);
+
+  return static_cast<bool>(nLambdaCandidates);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::Init() { fpTopoReconstructor->SetTarget({float(fOrigin[0]), float(fOrigin[1]), float(fOrigin[2])}); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::InitTrackParamVectors(const ca::Vector<ca::Track>& tracks)
+{
+  fvTrackParam.clear();
+  fvTrackParam.reserve(tracks.size());
+  std::transform(tracks.begin(), tracks.end(), std::back_inserter(fvTrackParam),
+                 [&](const auto& t) { return std::make_pair(t.fParFirst, t.fParLast); });
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+KFVertex V0Finder::MakeKfpPrimaryVertex(const std::array<float, 3>& r)
+{
+  KFVertex kfVertex;
+  kfVertex.X() = r[0];
+  kfVertex.Y() = r[1];
+  kfVertex.Z() = r[2];
+  for (int iC = 0; iC < 6; ++iC) {
+    kfVertex.Covariance(iC) = 0.f;
+  }
+  kfVertex.Chi2() = -100.f;
+  return kfVertex;
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
 CbmEventTriggers V0Finder::Process(const RecoResults& recoEvent)
 {
+  //L_(info) << "----------------------------- EVENT --------------";
   CbmEventTriggers res;
   fEventMonitor.Reset();
-  fEventMonitor.StartTimer(ETimer::Event);
+  fEventMonitor.StartTimer(ETimer::ProcessEvent);
   fEventMonitor.IncrementCounter(ECounter::EventsTotal);
+  fEventMonitor.IncrementCounter(ECounter::TracksTotal, recoEvent.tracks.size());
+
+  // ----- Initialize data-structures
+  fvParticleInfo.clear();
+  fvParticleInfo.resize(recoEvent.tracks.size());
 
   // ----- Define T0
   // So far we cannot preselect a hit from multiple ones, we will be using all of them iteratively to find lambdas
-  auto vT0 = CollectT0(recoEvent.bmonHits[fBmonPartitionIndex]);
-  if (vT0.empty()) {
+  fEventMonitor.StartTimer(ETimer::CollectT0);
+  CollectT0(recoEvent.bmonHits[fBmonPartitionIndex]);
+  if (fvT0s.empty()) {
     fEventMonitor.IncrementCounter(ECounter::EventsWoTzero);
     return res;
   }
+  fEventMonitor.StopTimer(ETimer::CollectT0);
 
-  //L_(info) << "------- Event: ";
-
-  // ----- Estimate DCA of tracks
+  // ----- Estimate DCA of tracks and assign PID
   // If a track has less then two STS hits, and undefined DCA value is stored
-  std::vector<double> vDca = CollectDca(recoEvent);
+  fEventMonitor.StartTimer(ETimer::CollectDca);
+  CollectDca(recoEvent);
+  fEventMonitor.StopTimer(ETimer::CollectDca);
 
-  // ***** DEBUG: BEGIN
-  //for (auto dca: vDca) {
-  //  L_(info) << "dca=" << dca;
-  //}
-  // ***** DEBUG: END
+  // ----- Try to find lambdas for different T0
+  fSelectedT0 = std::numeric_limits<double>::quiet_NaN();
+  fEventMonitor.StartTimer(ETimer::FindV0Candidates);
+  for (double t0 : fvT0s) {
+    if (FindV0Candidates(recoEvent, t0)) {
+      fSelectedT0 = t0;
+      res.Set(CbmEventTriggers::ETrigger::Lambda);
+      fEventMonitor.IncrementCounter(ECounter::EventsLambdaCand);
+      break;  // Lambda-candidates were found, there is no sense to scan further
+    }
+  }
+  fEventMonitor.StopTimer(ETimer::FindV0Candidates);
+  fEventMonitor.StopTimer(ETimer::ProcessEvent);
+  return res;
+}
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool V0Finder::SelectTrack(const ParticleInfo& particleInfo) const
+{
+  // Speed cut
+  if (particleInfo.fPdg == -211) {
+    if (particleInfo.fBeta < fMinBetaPion || particleInfo.fBeta > fMaxBetaPion) {
+      return false;
+    }
+  }
+  else if (particleInfo.fPdg == 2212) {
+    if (particleInfo.fBeta < fMinBetaProton || particleInfo.fBeta > fMaxBetaProton) {
+      return false;
+    }
+  }
+  return true;
+}
 
-  fEventMonitor.StopTimer(ETimer::Event);
-  return res;
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0Finder::SetKfpTrackParameters(KFPTrackVector& trackVector, uint32_t iKfpTrk, uint32_t iCaTrk,
+                                     const ca::Track::TrackParam_t& trkParam, const ParticleInfo& particleInfo) const
+{
+  // ----- Parameter definition
+  double tx{trkParam.GetTx()};
+  double ty{trkParam.GetTy()};
+  double qp{trkParam.GetQp()};
+  double p{particleInfo.fCharge / qp};
+  double p2{p * p};
+  double t2inv{1. / (1. + tx * tx + ty * ty)};
+  double pz{std::sqrt(t2inv * p2)};
+  double px{tx * pz};
+  double py{ty * pz};
+
+  trackVector.SetParameter(trkParam.GetX(), 0, iKfpTrk);
+  trackVector.SetParameter(trkParam.GetY(), 1, iKfpTrk);
+  trackVector.SetParameter(trkParam.GetZ(), 2, iKfpTrk);
+  trackVector.SetParameter(px, 3, iKfpTrk);
+  trackVector.SetParameter(py, 4, iKfpTrk);
+  trackVector.SetParameter(pz, 5, iKfpTrk);
+
+  // Jacobian matrix for (tx, ty, qp) -> (px, py, pz)
+  std::array<std::array<double, 3>, 3> Jp;
+  Jp[2][0] = -t2inv * px;         // d(pz)/d(tx)
+  Jp[2][1] = -t2inv * py;         // d(pz)/d(ty)
+  Jp[2][2] = -pz / qp;            // d(pz)/d(qp)
+  Jp[0][0] = tx * Jp[2][0] + pz;  // d(px)/d(tx)
+  Jp[0][1] = tx * Jp[2][1];       // d(px)/d(ty)
+  Jp[0][2] = tx * Jp[2][2];       // d(px)/d(qp)
+  Jp[1][0] = ty * Jp[2][0];       // d(py)/d(tx)
+  Jp[1][1] = ty * Jp[2][1] + pz;  // d(py)/d(ty)
+  Jp[1][2] = ty * Jp[2][2];       // d(py)/d(qp)
+
+
+  // ----- Covariance matrix definition
+  // Position covariance
+  trackVector.SetCovariance(trkParam.C00(), 0, iKfpTrk);  // var(x)
+  trackVector.SetCovariance(trkParam.C01(), 1, iKfpTrk);  // cov(x, y)
+  trackVector.SetCovariance(trkParam.C11(), 2, iKfpTrk);  // var(y)
+
+  // Momentum-position covariances
+  auto MomPosCovariance = [&](const int k, const int l) constexpr->double
+  {
+    double val{0.};
+    const auto& JpA = Jp[k];
+    for (int i = 0; i < 3; ++i) {
+      val += JpA[i] * trkParam.C(i + 2, l);
+    }
+    return val;
+  };
+  trackVector.SetCovariance(MomPosCovariance(0, 0), 6, iKfpTrk);   // cov(x, px)
+  trackVector.SetCovariance(MomPosCovariance(0, 1), 7, iKfpTrk);   // cov(y, px)
+  trackVector.SetCovariance(MomPosCovariance(1, 0), 10, iKfpTrk);  // cov(x, py)
+  trackVector.SetCovariance(MomPosCovariance(1, 1), 11, iKfpTrk);  // cov(y, py)
+  trackVector.SetCovariance(MomPosCovariance(2, 0), 15, iKfpTrk);  // cov(x, pz)
+  trackVector.SetCovariance(MomPosCovariance(2, 1), 16, iKfpTrk);  // cov(y, pz)
+
+  // Momentum covariances
+  auto MomentumCovariance = [&](const int k, const int l) constexpr->double
+  {
+    double val{0.};
+    const auto& JpA = Jp[k];
+    const auto& JpB = Jp[l];
+    for (int i = 0; i < 3; ++i) {
+      double factor{0.};
+      for (int j = 0; j < 3; ++j) {
+        factor += JpB[j] * trkParam.C(i + 2, j + 2);
+      }
+      val += JpA[i] * factor;
+    }
+    return val;
+  };
+  trackVector.SetCovariance(MomentumCovariance(0, 0), 9, iKfpTrk);   // var(px)
+  trackVector.SetCovariance(MomentumCovariance(1, 0), 13, iKfpTrk);  // cov(px, py)
+  trackVector.SetCovariance(MomentumCovariance(1, 1), 14, iKfpTrk);  // var(py)
+  trackVector.SetCovariance(MomentumCovariance(2, 0), 18, iKfpTrk);  // cov(px, pz)
+  trackVector.SetCovariance(MomentumCovariance(2, 1), 19, iKfpTrk);  // cov(py, pz)
+  trackVector.SetCovariance(MomentumCovariance(2, 2), 20, iKfpTrk);  // var(pz)
+
+  // Zero covariances (with z-coordinate)
+  trackVector.SetCovariance(0.f, 3, iKfpTrk);   // cov(x,z)
+  trackVector.SetCovariance(0.f, 4, iKfpTrk);   // cov(y,z)
+  trackVector.SetCovariance(0.f, 5, iKfpTrk);   // var(z)
+  trackVector.SetCovariance(0.f, 8, iKfpTrk);   // cov(z,px)
+  trackVector.SetCovariance(0.f, 12, iKfpTrk);  // cov(z,py)
+  trackVector.SetCovariance(0.f, 17, iKfpTrk);  // var(z,pz)
+
+  // ----- Other quantities
+  // Magnetic field (NOTE: zero fom mCBM)
+  // FIXME: Provide a proper initialization for full CBM
+  for (int iF = 0; iF < 10; ++iF) {
+    trackVector.SetFieldCoefficient(0.f, iF, iKfpTrk);
+  }
+
+  trackVector.SetId(iCaTrk, iKfpTrk);
+  trackVector.SetPDG(particleInfo.fPdg, iKfpTrk);
+  trackVector.SetQ(particleInfo.fCharge, iKfpTrk);
+  trackVector.SetNPixelHits(0, iKfpTrk);
+
+  // NOTE: 0 - primary tracks, -1 - secondary tracks. Here for now we assign ALL tracks as secondary
+  trackVector.SetPVIndex(-1, iKfpTrk);
 }
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index ef8b1e22b..0272b0ab8 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -24,6 +24,18 @@ namespace cbm::algo::kfp
   /// \brief A V0-finding algorithm
   class V0Finder {
    public:
+    /// \struct ParticleInfo
+    /// \brief  A structure to keep temporary PID information for tracks
+    struct ParticleInfo {
+      double fMass{std::numeric_limits<double>::quiet_NaN()};  //< Estimated mass of the particle [GeV/c2]
+      double fDca{std::numeric_limits<double>::quiet_NaN()};   //< DCA to the origin [cm]
+      double fBeta{std::numeric_limits<double>::quiet_NaN()};  //< Speed of the particle [c]
+      double fQp{std::numeric_limits<double>::quiet_NaN()};    //< q/p of the particle [GeV^-1]
+      int32_t fPdg{V0Finder::kUndefPdg};                       //< PDG code
+      int32_t fCharge{0};                                      //< Charge of the particle
+      bool fbSelected{false};                                  //< The track was selected
+    };
+
     /// \brief Default constructor
     V0Finder() = default;
 
@@ -46,14 +58,30 @@ namespace cbm::algo::kfp
     /// \param  pdg  A PDG code of the particle to be reconstructed
     void AddDecayToReconstructionList(int pdg) { GetKFParticleFinder()->AddDecayToReconstructionList(pdg); }
 
+    /// \brief  Gets monitor data
+    const V0FinderMonitorData_t& GetEventMonitor() const { return fEventMonitor; }
+
+    /// \brief  Gets origin
+    const std::array<float, 3>& GetOrigin() const { return fOrigin; }
+
     /// \brief  Mutable access to the KfParticleFinder of the run topology reconstructor
     KFParticleFinder* GetKFParticleFinder() { return fpTopoReconstructor->GetKFParticleFinder(); }
 
     /// \brief  Constant access to the KfParticleFinder of the run topology reconstructor
     const KFParticleFinder* GetKFParticleFinder() const { return fpTopoReconstructor->GetKFParticleFinder(); }
 
-    /// \brief  Gets monitor data
-    const V0FinderMonitorData_t& GetEventMonitor() const { return fEventMonitor; }
+    /// \brief  Gets a vector of particle info
+    const std::vector<ParticleInfo> GetParticleInfo() const { return fvParticleInfo; }
+
+    /// \brief  Gets selected t0
+    /// \note   NaN, if Lambda-candidate was not found
+    double GetSelectedT0() const { return fSelectedT0; }
+
+    /// \brief  Gets indices of selected tracks
+    const std::vector<uint32_t>& GetSelectedTrackIds() const { return fvSelectedTrackIds; }
+
+    /// \brief  Gets found t0s
+    const std::vector<double>& GetT0s() const { return fvT0s; }
 
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
@@ -77,7 +105,7 @@ namespace cbm::algo::kfp
     /// \param  y  Y-coordinate of the origin [cm]
     /// \param  z  Z-coordinate of the origin [cm]
     // FIXME: for now origin is defined as the target center, later it can be changed
-    void SetOrigin(double x, double y, double z) { fOrigin = {x, y, z}; }
+    void SetOrigin(double x, double y, double z) { fOrigin = {float(x), float(y), float(z)}; }
 
     /// \brief  Sets minimal pion DCA to primary vertex
     /// \brief  Sets pion velocity range
@@ -110,7 +138,6 @@ namespace cbm::algo::kfp
     /// \param  offset  An offset [ns]
     void SetTzeroOffset(double offset) { fTzeroOffset = offset; }
 
-    //* Specific parameters for the KFParticleFinder
     /// \brief  Sets cut on the distance to the primary vertex from the decay vertex
     /// \param  cut  Cut value [cm]
     void SetLCut(float cut) { GetKFParticleFinder()->SetLCut(cut); }
@@ -128,17 +155,40 @@ namespace cbm::algo::kfp
     void SetLdLCut2D(float cut) { GetKFParticleFinder()->SetLdLCut2D(cut); }
 
    private:
+    /// \brief  Assigns momentum based on the TOF measurement
+    /// \param[in]  tofHits      Tof hits container
+    /// \param[in]  tofHitIds    Tof hit indices, used by the track
+    /// \param[in]  t0           A t0 value
+    /// \param[inout]  pidInfo   PID information for the track
+    /// \return  true  A physically reasonable momentum assigned
+    /// \return  false Momentum was not assigned, because it was nonphysical
+    /* clang-format off */
+    bool AssignMomentum(const PartitionedVector<tof::Hit>& tofHits, 
+                        const std::vector<RecoResults::HitId_t>& tofHitIds,
+                        double t0, 
+                        ParticleInfo& pidInfo);
+    /* clang-format on */
+
+    /// \brief  Assigns PID info based on the estimated DCA
+    /// \param  dca  DCA of track to origin
+    /// \return (mass, charge, pid)
+    void AssignPid(ParticleInfo& info);
+
+    /// \brief  Collects a vector of DCA
+    /// \param  recoEvent  Instance of a reconstructed event
+    void CollectDca(const RecoResults& recoEvent);
+
     /// \brief  Collects T0 values among the BMON hits
     /// \param  bmonHits  A span of BMON hits
-    /// \return A vector of T0-s
     ///
     /// If multiple T0-s are found, the routine will run multiple times, until V0-candidates are found
-    std::vector<double> CollectT0(gsl::span<const bmon::Hit> bmonHits) const;
+    void CollectT0(gsl::span<const bmon::Hit> bmonHits);
 
-    /// \brief  Collects a vector of DCA
-    /// \param  recoEvent  Instance of a reconstructed event
-    /// \return A vector of DCAs to origin
-    std::vector<double> CollectDca(const RecoResults& recoEvent) const;
+    /// \brief  Estimates speed of particle, using TOF measurement
+    /// \param  tofHit   A TOF hit
+    /// \param  t0       An t0 value
+    /// \return Speed of particle [c]
+    double EstimateBeta(const tof::Hit& tofHit, double t0) const;
 
     /// \brief  Estimate DCA of a track to origin
     /// \param  fst  first  STS hit
@@ -146,29 +196,68 @@ namespace cbm::algo::kfp
     /// \return dca [cm]
     double EstimateDca(const sts::Hit& fst, const sts::Hit& snd) const;
 
-    //* Physical constants
+    /// \brief  Tries to find V0-candidates for a given t0
+    /// \param  recoEvent  Instance of a reconstructed event
+    /// \param  t0         An estimated t0 value
+    /// \return true  V0-candidates found
+    /// \return false V0-candidates not found
+    bool FindV0Candidates(const RecoResults& recoEvent, double t0);
+
+    /// \brief  Initializes copies of track parameter vectors
+    /// \param  tracks  A container of tracks
+    void InitTrackParamVectors(const ca::Vector<ca::Track>& tracks);
+
+    /// \brief   Makes a KF vertex
+    /// \param   r  coordinates of PV [cm]
+    static KFVertex MakeKfpPrimaryVertex(const std::array<float, 3>& r);
+
+    /// \brief  Applies selection cut on the track
+    /// \param  particleInfo  Particle collected information
+    /// \return true  Track is selected
+    /// \return false Track is rejected
+    bool SelectTrack(const ParticleInfo& particleInfo) const;
+
+    /// \brief  Sets KFP track parameters
+    /// \param[inout]  kfpTrkVector  Reference to the KFP track vector
+    /// \param[in]     iKfpTrk       Index of the KFP track
+    /// \param[in]     iCaTrk        Index of the CA track
+    /// \param[in]     trkParam      Track parameters
+    /// \param[in]     particleInfo  Particle information
+    void SetKfpTrackParameters(KFPTrackVector& kfpTrkVector, uint32_t iKfpTrk, uint32_t iCaTrk,
+                               const ca::Track::TrackParam_t& trkParam, const ParticleInfo& particleInfo) const;
+
+    //* Framework and physical constants
     static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
     static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
     static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
+    static constexpr int32_t kUndefPdg{-2};             ///< PDG for tracks, which PID cannot be inferred
+    static constexpr bool kUseAverageSpeed{false};      ///< If an average speed of tof hits is used
 
-
-    V0FinderMonitorData_t fEventMonitor;  ///< Monitor data instance (per event)
+    V0FinderMonitorData_t fEventMonitor;  ///< Main monitor data instance
 
     //* Different run-time cuts and flags (TODO: define in a config)
     double fTzeroOffset{0.};             ///< Offset for T0
     double fMinPionDca{1.5};             ///< Minimum DCA to PV for pions
     double fMinProtonDca{0.5};           ///< Minimum DCA to PV for protons
     double fQpAssignedUncertainty{0.1};  ///< Assigned relative uncertainty for q/p estimation
-    int fPrimaryAssignedPdg{321};        ///< Assigned PDG hypothesis for primary particles
     double fMinBetaProton{0.};           ///< Minimal proton velocity (beta) [c]
     double fMaxBetaProton{1.};           ///< Maximal proton velocity (beta) [c]
     double fMinBetaPion{0.};             ///< Minimal proton velocity (beta) [c]
     double fMaxBetaPion{1.};             ///< Maximal proton velocity (beta) [c]
-
+    int fPrimaryAssignedPdg{321};        ///< Assigned PDG hypothesis for primary particles
 
     //* Run-time variables, provided by framework
-    int fBmonPartitionIndex{-1};                ///< Index of selected partition in BMON hit vector
-    std::array<double, 3> fOrigin{0., 0., 0.};  ///< Coordinates of origin [cm]
+    int fBmonPartitionIndex{-1};  ///< Index of selected partition in BMON hit vector
+
+    //* Temporary data arrays (NOTE: keep them here for QA)
+    std::array<float, 3> fOrigin{0.f, 0.f, 0.f};                   ///< Coordinates of origin [cm]
+    std::vector<double> fvT0s;                                     ///< Found t0s [ns] (in event)
+    std::vector<ParticleInfo> fvParticleInfo;                      ///< PID info of tracks (in event)
+    std::vector<uint32_t> fvSelectedTrackIds;                      ///< IDs of selected tracks (in event)
+    double fSelectedT0{std::numeric_limits<double>::quiet_NaN()};  ///< A t0 value selected by the lambda-finder
+
+    /// \brief A copy of track parameters (first, last)
+    std::vector<std::pair<ca::Track::TrackParam_t, ca::Track::TrackParam_t>> fvTrackParam;
 
     //* Auxilary variables
 
diff --git a/algo/kfp/KfpV0FinderConfig.cxx b/algo/kfp/KfpV0FinderConfig.cxx
index 54e08ec32..d74e4d8b2 100644
--- a/algo/kfp/KfpV0FinderConfig.cxx
+++ b/algo/kfp/KfpV0FinderConfig.cxx
@@ -9,6 +9,7 @@
 
 #include "KfpV0FinderConfig.h"
 
+#include <iomanip>
 #include <sstream>
 
 using cbm::algo::kfp::Cuts;
@@ -58,12 +59,17 @@ std::string Cuts::ToString() const
 //
 std::string V0FinderConfig::ToString() const
 {
+  using std::dec;
+  using std::hex;
+  using std::setfill;
+  using std::setw;
   std::stringstream msg;
   msg << "\n-------------- KFP V0-finder Configuration: ------------------------------------------------";
   msg << "\nGOAL V0 (PDG): " << reconstructPdg;
   msg << '\n' << cuts.ToString();
   msg << "\nOTHER PARAMETERS:";
   msg << "\n\tt0-offset:                " << tZeroOffset << " [ns]";
+  msg << "\n\tReference BMON:           0x" << hex << setw(8) << setfill('0') << bmonAddress << dec;
   msg << "\n\tq/p relative uncertainty: " << qpAssignedUncertainty;
   msg << "\n\tassigned PDG of primary:  " << primaryAssignedPdg;
   msg << "\n--------------------------------------------------------------------------------------------";
diff --git a/algo/kfp/KfpV0FinderMonitor.h b/algo/kfp/KfpV0FinderMonitor.h
index 1f63bb246..5bb4311b6 100644
--- a/algo/kfp/KfpV0FinderMonitor.h
+++ b/algo/kfp/KfpV0FinderMonitor.h
@@ -26,6 +26,8 @@ namespace cbm::algo::kfp
     TracksWoPid,                ///< Tracks, which has undefined PID
     TracksWoMomentum,           ///< Tracks, which has no momentum
     TracksWUnphysicalBeta,      ///< Tracks with beta > 1
+    PionsDca,                   ///< Number of raw pion-candidates
+    ProtonsDca,                 ///< Number of raw proton-candidates
     Pions,                      ///< Number of pion-candidates
     Protons,                    ///< Number of proton-candidates
     EventsTotal,                ///< Total number of events
@@ -38,11 +40,20 @@ namespace cbm::algo::kfp
 
   /// \enum  ETimer
   /// \brief Timer keys for the V0FinderMonitor
+  /* clang-format off */
   enum class ETimer
   {
-    Event,  ///< Timer to process a single event
+    ProcessEvent,           ///< Processing of a single event
+      CollectT0,            ///< Collecting T0s
+      CollectDca,           ///< Estimating DCAs
+      FindV0Candidates,     ///< V0-finder procedure for a given t0
+        PrepareContainers,  ///< Prepare data containers
+        PreselectTracks,    ///< Track preselection
+        InitKfp,            ///< Init KFParticleFinder inside the event
+        ExecKfp,            ///< Run KFParticleFinder inside the event
     END
   };
+  /* clang-format on */
 
   /// \brief Specification of ca::MonitorData for the V0Finder
   using V0FinderMonitorData_t = ca::MonitorData<ECounter, ETimer>;
@@ -63,6 +74,8 @@ namespace cbm::algo::kfp
       SetCounterName(ECounter::TracksWoPid, "tracks w/o PID");
       SetCounterName(ECounter::TracksWoMomentum, "tracks w/o momentum");
       SetCounterName(ECounter::TracksWUnphysicalBeta, "tracks w/ beta > 1");
+      SetCounterName(ECounter::PionsDca, "raw pion candidates");
+      SetCounterName(ECounter::ProtonsDca, "raw proton candidates");
       SetCounterName(ECounter::Pions, "pion candidates");
       SetCounterName(ECounter::Protons, "proton candidates");
       SetCounterName(ECounter::EventsTotal, "all events");
@@ -71,7 +84,14 @@ namespace cbm::algo::kfp
       SetCounterName(ECounter::KfpEventsLambdaCand, "events w/ lambda candidates");
       SetCounterName(ECounter::KfpLambdaCandidates, "lambda candidates");
 
-      SetTimerName(ETimer::Event, "event processing");
+      SetTimerName(ETimer::ProcessEvent, "event processing");
+      SetTimerName(ETimer::CollectT0, "t0 container preparation");
+      SetTimerName(ETimer::CollectDca, "DCA container preparation");
+      SetTimerName(ETimer::FindV0Candidates, "V0-candidates finding");
+      SetTimerName(ETimer::PrepareContainers, "Container initialization");
+      SetTimerName(ETimer::PreselectTracks, "Track preselection");
+      SetTimerName(ETimer::InitKfp, "KFParticleFinder initialization");
+      SetTimerName(ETimer::ExecKfp, "KFParticleFinder execution");
     }
 
    private:
-- 
GitLab


From cfaa652d31fd51cbbb41716114a9a2b77687e27e Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 13 Feb 2025 07:00:31 +0100
Subject: [PATCH 14/17] online: QA for V0 finder

---
 algo/CMakeLists.txt                   |  1 +
 algo/base/Definitions.h               |  4 +-
 algo/global/Reco.cxx                  |  7 +-
 algo/kfp/KfpV0Finder.cxx              |  8 +--
 algo/kfp/KfpV0Finder.h                | 20 ++++--
 algo/kfp/KfpV0FinderChain.cxx         | 17 +++++
 algo/kfp/KfpV0FinderChain.h           | 20 +++++-
 algo/kfp/KfpV0FinderQa.cxx            | 97 +++++++++++++++++++++++++++
 algo/kfp/KfpV0FinderQa.h              | 82 ++++++++++++++++++++++
 algo/qa/hitfind/BmonHitfindQa.h       |  3 -
 services/histserv/app/Application.cxx | 20 +++---
 11 files changed, 252 insertions(+), 27 deletions(-)
 create mode 100644 algo/kfp/KfpV0FinderQa.cxx
 create mode 100644 algo/kfp/KfpV0FinderQa.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 9e7f23db7..a70b0872c 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -165,6 +165,7 @@ set(SRCS
   kfp/KfpV0Finder.cxx
   kfp/KfpV0FinderChain.cxx
   kfp/KfpV0FinderConfig.cxx
+  kfp/KfpV0FinderQa.cxx
 )
 
 set(BUILD_INFO_CXX ${CMAKE_CURRENT_BINARY_DIR}/base/BuildInfo.cxx)
diff --git a/algo/base/Definitions.h b/algo/base/Definitions.h
index 6c6d77ba8..b1ff4c9cc 100644
--- a/algo/base/Definitions.h
+++ b/algo/base/Definitions.h
@@ -78,6 +78,7 @@ namespace cbm::algo
     RecoTof,
     RecoFsd,
     Tracking,
+    V0Finder,
   };
 
 }  // namespace cbm::algo
@@ -148,7 +149,8 @@ CBM_ENUM_DICT(cbm::algo::QaStep,
   {"RecoMuch", cbm::algo::QaStep::RecoMuch},
   {"RecoTof", cbm::algo::QaStep::RecoTof},
   {"RecoFsd", cbm::algo::QaStep::RecoFsd},
-  {"Tracking", cbm::algo::QaStep::Tracking}
+  {"Tracking", cbm::algo::QaStep::Tracking},
+  {"V0Finder", cbm::algo::QaStep::V0Finder}
 );
 
 #endif
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index b2a735559..f312ebaaa 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -275,7 +275,12 @@ void Reco::Init(const Options& opts)
     fTrackingEvent->SetContext(&fContext);
     fTrackingEvent->Init();
 
-    fV0Finder = std::make_unique<V0FinderChain>();
+    if (fQaManager != nullptr && Opts().Has(QaStep::V0Finder)) {
+      fV0Finder = std::make_unique<V0FinderChain>(fQaManager);
+    }
+    else {
+      fV0Finder = std::make_unique<V0FinderChain>();
+    }
     fV0Finder->SetContext(&fContext);
     fV0Finder->SetBmonDefinedAddresses(fBmonHitFinder->GetDiamondAddresses());
     fV0Finder->Init();
diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index 2488ba89e..bc06d7800 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -95,10 +95,10 @@ void V0Finder::CollectDca(const RecoResults& recoEvent)
       fEventMonitor.IncrementCounter(ECounter::TracksWoStsHits);
       continue;
     }
-    auto& particleInfo        = fvParticleInfo[iTrk];
-    auto [iPtFst, iHitFst]    = stsHitIndicesInTrack[0];
-    auto [iPtSnd, iHitSnd]    = stsHitIndicesInTrack[1];
-    fvParticleInfo[iTrk].fDca = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
+    auto& particleInfo     = fvParticleInfo[iTrk];
+    auto [iPtFst, iHitFst] = stsHitIndicesInTrack[0];
+    auto [iPtSnd, iHitSnd] = stsHitIndicesInTrack[1];
+    particleInfo.fDca      = EstimateDca(recoEvent.stsHits[iPtFst][iHitFst], recoEvent.stsHits[iPtSnd][iHitSnd]);
     AssignPid(particleInfo);
   }
 }
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index 0272b0ab8..72d1d1c57 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -36,6 +36,12 @@ namespace cbm::algo::kfp
       bool fbSelected{false};                                  //< The track was selected
     };
 
+    //* Framework and physical constants (public)
+    static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
+    static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
+    static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
+    static constexpr int32_t kUndefPdg{-2};             ///< PDG for tracks, which PID cannot be inferred
+
     /// \brief Default constructor
     V0Finder() = default;
 
@@ -71,7 +77,7 @@ namespace cbm::algo::kfp
     const KFParticleFinder* GetKFParticleFinder() const { return fpTopoReconstructor->GetKFParticleFinder(); }
 
     /// \brief  Gets a vector of particle info
-    const std::vector<ParticleInfo> GetParticleInfo() const { return fvParticleInfo; }
+    const std::vector<ParticleInfo>& GetParticleInfo() const { return fvParticleInfo; }
 
     /// \brief  Gets selected t0
     /// \note   NaN, if Lambda-candidate was not found
@@ -83,6 +89,9 @@ namespace cbm::algo::kfp
     /// \brief  Gets found t0s
     const std::vector<double>& GetT0s() const { return fvT0s; }
 
+    /// \brief  Accessor to topology reconstructor
+    const std::unique_ptr<KFParticleTopoReconstructor>& GetTopoReconstructor() const { return fpTopoReconstructor; }
+
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
@@ -226,12 +235,9 @@ namespace cbm::algo::kfp
     void SetKfpTrackParameters(KFPTrackVector& kfpTrkVector, uint32_t iKfpTrk, uint32_t iCaTrk,
                                const ca::Track::TrackParam_t& trkParam, const ParticleInfo& particleInfo) const;
 
-    //* Framework and physical constants
-    static constexpr double kPionMass{0.13957039};      ///< Pion mass [GeV/c2]
-    static constexpr double kProtonMass{0.938272088};   ///< Proton mass [GeV/c2]
-    static constexpr double kSpeedOfLight{29.9792458};  ///< Speed of light [cm/ns]
-    static constexpr int32_t kUndefPdg{-2};             ///< PDG for tracks, which PID cannot be inferred
-    static constexpr bool kUseAverageSpeed{false};      ///< If an average speed of tof hits is used
+
+    //* Framework and physical constants (private)
+    static constexpr bool kUseAverageSpeed{false};  ///< If an average speed of tof hits is used
 
     V0FinderMonitorData_t fEventMonitor;  ///< Main monitor data instance
 
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
index f41a0597a..e23bc9e81 100644
--- a/algo/kfp/KfpV0FinderChain.cxx
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -18,6 +18,13 @@
 
 using cbm::algo::V0FinderChain;
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+V0FinderChain::V0FinderChain(const std::unique_ptr<qa::Manager>& qaManager)
+  : fpFinderQa(qaManager != nullptr ? std::make_unique<kfp::V0FinderQa>(qaManager, "V0Finder") : nullptr)
+{
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void V0FinderChain::Finalize() { L_(info) << fMonitorRun.ToString(); }
@@ -69,6 +76,8 @@ try {
   }
   const auto& pion{particles[iPion]};
   const auto& proton{particles[iProton]};
+  L_(info) << "!!!!!!!!!!! > pion:   " << pion.minDca;
+  L_(info) << "!!!!!!!!!!! > proton: " << proton.minDca;
 
   // ----- Define a BMON diamond
   if (fBmonDefinedAddresses.empty()) {
@@ -114,6 +123,11 @@ try {
     //* Init the V0 finder
     fFinder.Init();
   }
+
+  if (fpFinderQa != nullptr) {
+    fpFinderQa->Init();
+  }
+
   L_(info) << "kfp::V0FinderChain: initializing the V0-finder chain ... done";
 }
 catch (const std::exception& err) {
@@ -128,6 +142,9 @@ V0FinderChain::EventOutput V0FinderChain::ProcessEvent(const RecoResults& recoEv
   EventOutput res;
   res.triggers = fFinder.Process(recoEvent);
   res.monitor  = fFinder.GetEventMonitor();
+  if (fpFinderQa != nullptr) {
+    fpFinderQa->Exec(recoEvent, fFinder);
+  }
   fMonitorRun.AddMonitorData(res.monitor);
   return res;
 }
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
index 630fc02e6..8053c93f7 100644
--- a/algo/kfp/KfpV0FinderChain.h
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -15,6 +15,15 @@
 #include "global/RecoResults.h"
 #include "kfp/KfpV0Finder.h"
 #include "kfp/KfpV0FinderMonitor.h"
+#include "kfp/KfpV0FinderQa.h"
+
+namespace cbm::algo
+{
+  namespace qa
+  {
+    class Manager;
+  }
+}  // namespace cbm::algo
 
 namespace cbm::algo
 {
@@ -32,6 +41,10 @@ namespace cbm::algo
     /// \brief Default constructor
     V0FinderChain() = default;
 
+    /// \brief Constructor from parameters
+    /// \param pQaManager   A QA-manager
+    V0FinderChain(const std::unique_ptr<qa::Manager>& qaManager);
+
     /// \brief Copy constructor
     V0FinderChain(const V0FinderChain&) = delete;
 
@@ -61,8 +74,9 @@ namespace cbm::algo
     EventOutput ProcessEvent(const RecoResults& recoEvent);
 
    private:
-    kfp::V0Finder fFinder;                      ///< Instance of the V0-finding algorithm
-    kfp::V0FinderMonitor fMonitorRun;           ///< Monitor per run
-    PODVector<uint32_t> fBmonDefinedAddresses;  ///< Available addresses of BMON
+    kfp::V0Finder fFinder;                                 ///< Instance of the V0-finding algorithm
+    kfp::V0FinderMonitor fMonitorRun;                      ///< Monitor per run
+    std::unique_ptr<kfp::V0FinderQa> fpFinderQa{nullptr};  ///< QA module
+    PODVector<uint32_t> fBmonDefinedAddresses;             ///< Available addresses of BMON
   };
 }  // namespace cbm::algo
diff --git a/algo/kfp/KfpV0FinderQa.cxx b/algo/kfp/KfpV0FinderQa.cxx
new file mode 100644
index 000000000..4239174a7
--- /dev/null
+++ b/algo/kfp/KfpV0FinderQa.cxx
@@ -0,0 +1,97 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderQa.cxx
+/// \date   13.02.2025
+/// \brief  A V0 finding algorithm QA (implementation)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "kfp/KfpV0FinderQa.h"
+
+#include "global/RecoResults.h"
+#include "kfp/KfpV0Finder.h"
+
+
+using cbm::algo::kfp::V0FinderQa;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderQa::Init()
+{
+  using qa::CanvasConfig;
+  using qa::H1D;
+  using qa::PadConfig;
+
+  //* Histogram initialisation
+  fvphMassLambdaCand =
+    MakeObj<H1D>("kfp_mass_lambda", "Mass of #Lambda-candidates;m [GeV/c^{2}];Counts", kMassB, kMassL, kMassU);
+  fvphMassAll    = MakeObj<H1D>("kfp_mass_all", "Mass of particles;m [GeV/c^{2}];Counts", 300, 0., 1.5);
+  fvphDcaAll     = MakeObj<H1D>("kfp_dca_all", "DCA of tracks to origin;DCA [cm];Counts", kDcaB, kDcaL, kDcaU);
+  fvphBetaAll    = MakeObj<H1D>("kfp_beta_all", "Speed of tracks;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphBetaPdg    = MakeObj<H1D>("kfp_beta_pdg", "Speed of tracks w/ PDG;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphBetaPion   = MakeObj<H1D>("kfp_beta_pion", "Speed of #pi-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphBetaProton = MakeObj<H1D>("kfp_beta_proton", "Speed of proton-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
+
+  //* Canvas initialisation
+  auto canv = CanvasConfig("kfp_lambda", "Lambda-trigger summary QA", 4, 2);
+  canv.AddPadConfig(PadConfig(fvphMassLambdaCand, "hist"));  // (0, 0)
+  canv.AddPadConfig(PadConfig(fvphMassAll, "hist"));         // (1, 0)
+  canv.AddPadConfig(PadConfig(fvphDcaAll, "hist"));          // (0, 1)
+  canv.AddPadConfig(PadConfig(fvphBetaAll, "hist"));
+  canv.AddPadConfig(PadConfig(fvphBetaPdg, "hist"));
+  canv.AddPadConfig(PadConfig(fvphBetaPion, "hist"));
+  canv.AddPadConfig(PadConfig(fvphBetaProton, "hist"));
+  AddCanvasConfig(canv);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void V0FinderQa::Exec(const RecoResults& recoEvent, const V0Finder& v0Finder)
+{
+  //* Fill track distributions
+  const auto& tracks{recoEvent.tracks};
+  if (v0Finder.GetT0s().size() == 1) {
+    for (uint32_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {
+      const auto& particleInfo{v0Finder.GetParticleInfo()[iTrk]};
+      bool bPdgDefined = (particleInfo.fPdg != V0Finder::kUndefPdg);
+      fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
+      fvphBetaAll->Fill(particleInfo.fBeta);
+      if (bPdgDefined) {
+        fvphBetaPdg->Fill(particleInfo.fBeta);
+        if (particleInfo.fPdg == -211) {
+          fvphBetaPion->Fill(particleInfo.fBeta);
+        }
+        else if (particleInfo.fPdg == 2212) {
+          fvphBetaProton->Fill(particleInfo.fBeta);
+        }
+      }
+    }
+  }
+  else {
+    for (uint32_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {
+      const auto& particleInfo{v0Finder.GetParticleInfo()[iTrk]};
+      bool bPdgDefined = (particleInfo.fPdg != V0Finder::kUndefPdg);
+      fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
+      fvphBetaAll->Fill(-999);
+      if (bPdgDefined) {
+        fvphBetaPdg->Fill(-999);
+        if (particleInfo.fPdg == -211) {
+          fvphBetaPion->Fill(-999);
+        }
+        else if (particleInfo.fPdg == 2212) {
+          fvphBetaProton->Fill(-999);
+        }
+      }
+    }
+  }
+
+  //* Fill particle distributions
+  const auto& particles = v0Finder.GetTopoReconstructor()->GetParticles();
+  for (const auto& particle : particles) {
+    fvphMassAll->Fill(particle.GetMass());
+    if (particle.GetPDG() == 3122) {
+      fvphMassLambdaCand->Fill(particle.GetMass());
+    }
+  }
+}
diff --git a/algo/kfp/KfpV0FinderQa.h b/algo/kfp/KfpV0FinderQa.h
new file mode 100644
index 000000000..39a1289ba
--- /dev/null
+++ b/algo/kfp/KfpV0FinderQa.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   KfpV0FinderQa.h
+/// \date   13.02.2025
+/// \brief  A V0 finding algorithm QA
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "qa/QaTaskHeader.h"
+
+namespace cbm::algo
+{
+  class RecoResults;
+
+  namespace qa
+  {
+    class H1D;
+    class H2D;
+  }  // namespace qa
+
+  namespace kfp
+  {
+    class V0Finder;
+  }
+}  // namespace cbm::algo
+
+namespace cbm::algo::kfp
+{
+  /// \class V0FinderQa
+  /// \brief A QA-task for the V0-finding algorithm
+  class V0FinderQa : public qa::TaskHeader {
+   public:
+    /// \brief Constructor
+    /// \param pManager  Pointer to the QA manager
+    /// \param name      Name of the QA
+    V0FinderQa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
+
+    /// \brief Copy constructor
+    V0FinderQa(const V0FinderQa&) = delete;
+
+    /// \brief Move constructor
+    V0FinderQa(V0FinderQa&&) = delete;
+
+    /// \brief Copy assignment operator
+    V0FinderQa& operator=(const V0FinderQa&) = delete;
+
+    /// \brief Move assignment operator
+    V0FinderQa& operator=(V0FinderQa&&) = delete;
+
+    /// \brief Executes the task, fills the histograms
+    /// \param recoEvent  A reconstructed event instance
+    /// \param v0Finder   A V0-finder instance
+    void Exec(const RecoResults& recoEvent, const V0Finder& v0Finder);
+
+    /// \brief Initialized the task
+    void Init();
+
+   private:
+    //* Constants
+    static constexpr int kMassB    = 200;   ///< Lambda-candidate mass: number of bins
+    static constexpr double kMassL = 1.08;  ///< Lambda-candidate mass: lower bound [GeV/c2]
+    static constexpr double kMassU = 1.18;  ///< Lambda-candidate mass: upper bound [GeV/c2]
+    static constexpr int kDcaB     = 240;   ///< DCA to origin: number of bins
+    static constexpr double kDcaL  = 0.;    ///< DCA to origin: lower bound [cm]
+    static constexpr double kDcaU  = 12.;   ///< DCA to origin: upper bound [cm]
+    static constexpr int kBetaB    = 240;   ///< Speed of particle: number of bins
+    static constexpr double kBetaL = 0.;    ///< Speed of particle: lower bound [c]
+    static constexpr double kBetaU = 1.2;   ///< Speed of particle: upper bound [c]
+
+    //* Histograms
+    qa::H1D* fvphMassLambdaCand{nullptr};  ///< Mass of Lambda-candidates
+    qa::H1D* fvphMassAll{nullptr};         ///< Mass of all particles in the topology
+    qa::H1D* fvphDcaAll{nullptr};          ///< DCA of particles to origin
+    qa::H1D* fvphBetaAll{nullptr};         ///< Speed of all particles
+    qa::H1D* fvphBetaPdg{nullptr};         ///< Speed of particles with PID hypothesis
+    qa::H1D* fvphBetaPion{nullptr};        ///< Speed of pion-candidates
+    qa::H1D* fvphBetaProton{nullptr};      ///< Speed of proton-candidates
+  };
+}  // namespace cbm::algo::kfp
diff --git a/algo/qa/hitfind/BmonHitfindQa.h b/algo/qa/hitfind/BmonHitfindQa.h
index 020f62fa8..b2a8b3e30 100644
--- a/algo/qa/hitfind/BmonHitfindQa.h
+++ b/algo/qa/hitfind/BmonHitfindQa.h
@@ -34,8 +34,6 @@ namespace cbm::algo::bmon
 {
   /// \class HitfindQa
   /// \brief A QA module for the BMON hit-finder
-  /// \param pManager  Pointer to the QA manager
-  /// \param name      Name of the QA (directory)
   class HitfindQa : public qa::TaskHeader {
    public:
     /// \brief Constructor
@@ -44,7 +42,6 @@ namespace cbm::algo::bmon
     HitfindQa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
 
     /// \brief Constructor from the configuration object
-    /// \param config  QA configuration object
     HitfindQa() = default;
 
     /// \brief Copy constructor
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
index 5eb5362f0..d2215633e 100644
--- a/services/histserv/app/Application.cxx
+++ b/services/histserv/app/Application.cxx
@@ -800,17 +800,21 @@ bool Application::PrepareCanvas(uint32_t uCanvIdx)
       if ("nullptr" != sName) {
         TObject* pObj = fArrayHisto[FindHistogram(sName)];
 
-        if (nullptr != dynamic_cast<TProfile2D*>(pObj)) {
-          dynamic_cast<TProfile2D*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        if (auto* pHist = dynamic_cast<TProfile2D*>(pObj)) {
+          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (nullptr != dynamic_cast<TProfile*>(pObj)) {
-          dynamic_cast<TProfile*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHist = dynamic_cast<TProfile*>(pObj)) {
+          pHist->SetLineColor(uObjIdx + 1);
+          pHist->SetMarkerColor(uObjIdx + 1);
+          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (nullptr != dynamic_cast<TH2*>(pObj)) {
-          dynamic_cast<TH2*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHist = dynamic_cast<TH2*>(pObj)) {
+          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH2 *>( pObj ) )
-        else if (nullptr != dynamic_cast<TH1*>(pObj)) {
-          dynamic_cast<TH1*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHist = dynamic_cast<TH1*>(pObj)) {
+          pHist->SetLineColor(uObjIdx + 1);
+          pHist->SetMarkerColor(uObjIdx + 1);
+          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH1 *>( pObj ) )
         else
           LOG(warning) << "  Unsupported object type for " << sName << " when preparing canvas " << conf.GetName();
-- 
GitLab


From ee69fbcc364c38aa03b0dcfa8df67b3147b68de6 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 13 Feb 2025 07:32:47 +0100
Subject: [PATCH 15/17] online-kfp: bugfix

---
 algo/kfp/KfpV0Finder.cxx              | 10 +++++-----
 algo/kfp/KfpV0Finder.h                |  3 +++
 algo/kfp/KfpV0FinderQa.cxx            | 15 +++++++++------
 algo/kfp/KfpV0FinderQa.h              |  4 +++-
 services/histserv/app/Application.cxx | 24 ++++++++++++------------
 5 files changed, 32 insertions(+), 24 deletions(-)

diff --git a/algo/kfp/KfpV0Finder.cxx b/algo/kfp/KfpV0Finder.cxx
index bc06d7800..c95965bc8 100644
--- a/algo/kfp/KfpV0Finder.cxx
+++ b/algo/kfp/KfpV0Finder.cxx
@@ -50,7 +50,7 @@ bool V0Finder::AssignMomentum(const PartitionedVector<tof::Hit>& tofHits,
   }
   double gamma{1. / sqrt(1. - beta * beta)};
   particleInfo.fBeta = beta;
-  particleInfo.fQp   = particleInfo.fQp / (gamma * beta * particleInfo.fMass);
+  particleInfo.fQp   = particleInfo.fCharge / (gamma * beta * particleInfo.fMass);
   return true;
 }
 
@@ -91,7 +91,7 @@ void V0Finder::CollectDca(const RecoResults& recoEvent)
   const auto& stsHitIndices = recoEvent.trackStsHitIndices;
   for (size_t iTrk = 0; iTrk < stsHitIndices.size(); ++iTrk) {
     const auto& stsHitIndicesInTrack = stsHitIndices[iTrk];
-    if (stsHitIndicesInTrack.size() < 2) {  // less then two sts hits
+    if (stsHitIndicesInTrack.size() < 2) {  // DCA cannot be estimated
       fEventMonitor.IncrementCounter(ECounter::TracksWoStsHits);
       continue;
     }
@@ -130,7 +130,7 @@ double V0Finder::EstimateBeta(const tof::Hit& hit, double t0) const
   double t = hit.Time() - t0 - fTzeroOffset;
   double x{hit.X() - fOrigin[0]};
   double y{hit.Y() - fOrigin[1]};
-  double z{hit.Y() - fOrigin[2]};
+  double z{hit.Z() - fOrigin[2]};
   double x2{x * x};
   double y2{y * y};
   double z2{z * z};
@@ -217,7 +217,6 @@ bool V0Finder::FindV0Candidates(const RecoResults& recoEvent, double t0)
   KFPTrackVector kfpTracksLst;
   kfpTracksFst.Resize(nSelectedTracks);
   kfpTracksLst.Resize(nSelectedTracks);
-  fpTopoReconstructor->Init(kfpTracksFst, kfpTracksLst);
   for (uint32_t iKfpTrk = 0; iKfpTrk < fvSelectedTrackIds.size(); ++iKfpTrk) {  // Over selected tracks
     uint32_t iCaTrk{fvSelectedTrackIds[iKfpTrk]};
     const auto& trkParam{fvTrackParam[iCaTrk]};
@@ -225,6 +224,7 @@ bool V0Finder::FindV0Candidates(const RecoResults& recoEvent, double t0)
     SetKfpTrackParameters(kfpTracksFst, iKfpTrk, iCaTrk, trkParam.first, particleInfo);
     SetKfpTrackParameters(kfpTracksLst, iKfpTrk, iCaTrk, trkParam.second, particleInfo);
   }
+  fpTopoReconstructor->Init(kfpTracksFst, kfpTracksLst);
   fpTopoReconstructor->AddPV(MakeKfpPrimaryVertex(fOrigin));
   fpTopoReconstructor->SortTracks();
   fEventMonitor.StopTimer(ETimer::InitKfp);
@@ -308,7 +308,7 @@ CbmEventTriggers V0Finder::Process(const RecoResults& recoEvent)
     if (FindV0Candidates(recoEvent, t0)) {
       fSelectedT0 = t0;
       res.Set(CbmEventTriggers::ETrigger::Lambda);
-      fEventMonitor.IncrementCounter(ECounter::EventsLambdaCand);
+      fEventMonitor.IncrementCounter(ECounter::KfpEventsLambdaCand);
       break;  // Lambda-candidates were found, there is no sense to scan further
     }
   }
diff --git a/algo/kfp/KfpV0Finder.h b/algo/kfp/KfpV0Finder.h
index 72d1d1c57..9605b581b 100644
--- a/algo/kfp/KfpV0Finder.h
+++ b/algo/kfp/KfpV0Finder.h
@@ -92,6 +92,9 @@ namespace cbm::algo::kfp
     /// \brief  Accessor to topology reconstructor
     const std::unique_ptr<KFParticleTopoReconstructor>& GetTopoReconstructor() const { return fpTopoReconstructor; }
 
+    /// \brief  Gets track parameters
+    const auto& GetTrackAssignedParams() const { return fvTrackParam; }
+
     /// \brief Initializes the instance (called in the beginning of the run)
     void Init();
 
diff --git a/algo/kfp/KfpV0FinderQa.cxx b/algo/kfp/KfpV0FinderQa.cxx
index 4239174a7..ffc903e2a 100644
--- a/algo/kfp/KfpV0FinderQa.cxx
+++ b/algo/kfp/KfpV0FinderQa.cxx
@@ -29,17 +29,18 @@ void V0FinderQa::Init()
   fvphMassAll    = MakeObj<H1D>("kfp_mass_all", "Mass of particles;m [GeV/c^{2}];Counts", 300, 0., 1.5);
   fvphDcaAll     = MakeObj<H1D>("kfp_dca_all", "DCA of tracks to origin;DCA [cm];Counts", kDcaB, kDcaL, kDcaU);
   fvphBetaAll    = MakeObj<H1D>("kfp_beta_all", "Speed of tracks;#beta;Counts", kBetaB, kBetaL, kBetaU);
-  fvphBetaPdg    = MakeObj<H1D>("kfp_beta_pdg", "Speed of tracks w/ PDG;#beta;Counts", kBetaB, kBetaL, kBetaU);
   fvphBetaPion   = MakeObj<H1D>("kfp_beta_pion", "Speed of #pi-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
   fvphBetaProton = MakeObj<H1D>("kfp_beta_proton", "Speed of proton-candidates;#beta;Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomAll     = MakeObj<H1D>("kfp_mom_all", "Momentum of tracks;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomPion    = MakeObj<H1D>("kfp_mom_pion", "Momentum of #pi-candidates;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
+  fvphMomProton =
+    MakeObj<H1D>("kfp_mom_proton", "Momentum of proton-candidates;p [GeV/c];Counts", kBetaB, kBetaL, kBetaU);
 
   //* Canvas initialisation
-  auto canv = CanvasConfig("kfp_lambda", "Lambda-trigger summary QA", 4, 2);
+  auto canv = CanvasConfig("kfp_lambda", "Lambda-trigger summary QA", 3, 2);
   canv.AddPadConfig(PadConfig(fvphMassLambdaCand, "hist"));  // (0, 0)
-  canv.AddPadConfig(PadConfig(fvphMassAll, "hist"));         // (1, 0)
   canv.AddPadConfig(PadConfig(fvphDcaAll, "hist"));          // (0, 1)
   canv.AddPadConfig(PadConfig(fvphBetaAll, "hist"));
-  canv.AddPadConfig(PadConfig(fvphBetaPdg, "hist"));
   canv.AddPadConfig(PadConfig(fvphBetaPion, "hist"));
   canv.AddPadConfig(PadConfig(fvphBetaProton, "hist"));
   AddCanvasConfig(canv);
@@ -54,16 +55,18 @@ void V0FinderQa::Exec(const RecoResults& recoEvent, const V0Finder& v0Finder)
   if (v0Finder.GetT0s().size() == 1) {
     for (uint32_t iTrk = 0; iTrk < tracks.size(); ++iTrk) {
       const auto& particleInfo{v0Finder.GetParticleInfo()[iTrk]};
+      const auto& trkParFst{(v0Finder.GetTrackAssignedParams()[iTrk]).first};
       bool bPdgDefined = (particleInfo.fPdg != V0Finder::kUndefPdg);
       fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
       fvphBetaAll->Fill(particleInfo.fBeta);
       if (bPdgDefined) {
-        fvphBetaPdg->Fill(particleInfo.fBeta);
         if (particleInfo.fPdg == -211) {
           fvphBetaPion->Fill(particleInfo.fBeta);
+          fvphMomPion->Fill(trkParFst.GetP());
         }
         else if (particleInfo.fPdg == 2212) {
           fvphBetaProton->Fill(particleInfo.fBeta);
+          fvphMomProton->Fill(trkParFst.GetP());
         }
       }
     }
@@ -75,9 +78,9 @@ void V0FinderQa::Exec(const RecoResults& recoEvent, const V0Finder& v0Finder)
       fvphDcaAll->Fill(bPdgDefined ? particleInfo.fDca : -999);
       fvphBetaAll->Fill(-999);
       if (bPdgDefined) {
-        fvphBetaPdg->Fill(-999);
         if (particleInfo.fPdg == -211) {
           fvphBetaPion->Fill(-999);
+          fvphMomPion->Fill(-999);
         }
         else if (particleInfo.fPdg == 2212) {
           fvphBetaProton->Fill(-999);
diff --git a/algo/kfp/KfpV0FinderQa.h b/algo/kfp/KfpV0FinderQa.h
index 39a1289ba..35b914e18 100644
--- a/algo/kfp/KfpV0FinderQa.h
+++ b/algo/kfp/KfpV0FinderQa.h
@@ -75,8 +75,10 @@ namespace cbm::algo::kfp
     qa::H1D* fvphMassAll{nullptr};         ///< Mass of all particles in the topology
     qa::H1D* fvphDcaAll{nullptr};          ///< DCA of particles to origin
     qa::H1D* fvphBetaAll{nullptr};         ///< Speed of all particles
-    qa::H1D* fvphBetaPdg{nullptr};         ///< Speed of particles with PID hypothesis
     qa::H1D* fvphBetaPion{nullptr};        ///< Speed of pion-candidates
     qa::H1D* fvphBetaProton{nullptr};      ///< Speed of proton-candidates
+    qa::H1D* fvphMomAll{nullptr};          ///< Speed of all particles
+    qa::H1D* fvphMomPion{nullptr};         ///< Speed of pion-candidates
+    qa::H1D* fvphMomProton{nullptr};       ///< Speed of proton-candidates
   };
 }  // namespace cbm::algo::kfp
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
index d2215633e..a4978c303 100644
--- a/services/histserv/app/Application.cxx
+++ b/services/histserv/app/Application.cxx
@@ -800,21 +800,21 @@ bool Application::PrepareCanvas(uint32_t uCanvIdx)
       if ("nullptr" != sName) {
         TObject* pObj = fArrayHisto[FindHistogram(sName)];
 
-        if (auto* pHist = dynamic_cast<TProfile2D*>(pObj)) {
-          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        if (auto* pHistP2 = dynamic_cast<TProfile2D*>(pObj)) {
+          pHistP2->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (auto* pHist = dynamic_cast<TProfile*>(pObj)) {
-          pHist->SetLineColor(uObjIdx + 1);
-          pHist->SetMarkerColor(uObjIdx + 1);
-          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistP1 = dynamic_cast<TProfile*>(pObj)) {
+          pHistP1->SetLineColor(uObjIdx + 1);
+          pHistP1->SetMarkerColor(uObjIdx + 1);
+          pHistP1->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TProfile *>( pObj ) )
-        else if (auto* pHist = dynamic_cast<TH2*>(pObj)) {
-          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistH2 = dynamic_cast<TH2*>(pObj)) {
+          pHistH2->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH2 *>( pObj ) )
-        else if (auto* pHist = dynamic_cast<TH1*>(pObj)) {
-          pHist->SetLineColor(uObjIdx + 1);
-          pHist->SetMarkerColor(uObjIdx + 1);
-          pHist->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
+        else if (auto* pHistH1 = dynamic_cast<TH1*>(pObj)) {
+          pHistH1->SetLineColor(uObjIdx + 1);
+          pHistH1->SetMarkerColor(uObjIdx + 1);
+          pHistH1->Draw(conf.GetOption(uPadIdx, uObjIdx).data());
         }  // if( nullptr != dynamic_cast< TH1 *>( pObj ) )
         else
           LOG(warning) << "  Unsupported object type for " << sName << " when preparing canvas " << conf.GetName();
-- 
GitLab


From 5c51fa5c2cc510c2752f39b073b76a4f7f74f9f4 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Thu, 13 Feb 2025 16:56:58 +0100
Subject: [PATCH 16/17] online: integrating event selection triggers into
 framework

---
 algo/base/DigiData.cxx                     |  8 ++--
 algo/base/DigiData.h                       |  2 +
 algo/evselector/RecoEventSelectorMonitor.h |  6 +++
 algo/global/Reco.cxx                       | 36 +++++++--------
 algo/global/Reco.h                         |  4 +-
 algo/kfp/KfpV0FinderChain.cxx              | 24 +++++++---
 algo/kfp/KfpV0FinderChain.h                | 11 +++--
 algo/kfp/KfpV0FinderMonitor.h              |  2 +
 core/data/CbmEventTriggers.h               | 52 +++++++++++++++-------
 core/data/global/CbmDigiEvent.h            | 39 +++++++++-------
 10 files changed, 115 insertions(+), 69 deletions(-)

diff --git a/algo/base/DigiData.cxx b/algo/base/DigiData.cxx
index 179c8ca31..79fd5a483 100644
--- a/algo/base/DigiData.cxx
+++ b/algo/base/DigiData.cxx
@@ -119,14 +119,16 @@ DigiEvent::DigiEvent(const CbmDigiEvent& storable)
   : DigiData(storable.fData)
   , fNumber(storable.fNumber)
   , fTime(storable.fTime)
+  , fSelectionTriggers(storable.fSelectionTriggers)
 {
 }
 
 CbmDigiEvent DigiEvent::ToStorable() const
 {
   return CbmDigiEvent{
-    .fData   = DigiData::ToStorable(),
-    .fNumber = fNumber,
-    .fTime   = fTime,
+    .fData              = DigiData::ToStorable(),
+    .fNumber            = fNumber,
+    .fTime              = fTime,
+    .fSelectionTriggers = fSelectionTriggers,
   };
 }
diff --git a/algo/base/DigiData.h b/algo/base/DigiData.h
index c19b685d9..09b82d933 100644
--- a/algo/base/DigiData.h
+++ b/algo/base/DigiData.h
@@ -7,6 +7,7 @@
 #include "CbmBmonDigi.h"
 #include "CbmDigiData.h"
 #include "CbmDigiEvent.h"
+#include "CbmEventTriggers.h"
 #include "CbmFsdDigi.h"
 #include "CbmMuchDigi.h"
 #include "CbmPsdDigi.h"
@@ -80,6 +81,7 @@ namespace cbm::algo
     // FIXME: Event number not set yet!
     uint64_t fNumber = -1;  ///< Event identifier
     double fTime     = 0;   ///< Event trigger time [ns]
+    CbmEventTriggers fSelectionTriggers;
 
     static std::vector<DigiEvent> FromCbmDigiEvents(const std::vector<CbmDigiEvent>& events);
     static std::vector<CbmDigiEvent> ToCbmDigiEvents(const std::vector<DigiEvent>& events);
diff --git a/algo/evselector/RecoEventSelectorMonitor.h b/algo/evselector/RecoEventSelectorMonitor.h
index de2eb4a27..ae6524f80 100644
--- a/algo/evselector/RecoEventSelectorMonitor.h
+++ b/algo/evselector/RecoEventSelectorMonitor.h
@@ -19,12 +19,14 @@ namespace cbm::algo::evselect
   /// \brief Counter keys for the event selector monitor
   enum class ECounter
   {
+    Timeslices,        ///< number of processed timeslices
     EventsTotal,       ///< Total number of events processed
     EventsNeStsHits,   ///< Events with not enough STS hits
     EventsNeTofHits,   ///< Events with enough STS hits, but not enough TOF hits
     EventsNeBmonHits,  ///< Events with not enough BMon hits
     EventsNeTracks,    ///< Events with enough hits, but not enough tracks
     EventsSelected,    ///< Number of selected events
+    LambdaCandidates,  ///< Number of lambda-candidates, returned by KFParticleFinder
     END
   };
 
@@ -53,11 +55,13 @@ namespace cbm::algo::evselect
     /// \brief Default constructor
     Monitor() : ca::Monitor<ECounter, ETimer>("Event-selector Monitor")
     {
+      SetCounterName(ECounter::Timeslices, "processed timeslices");
       SetCounterName(ECounter::EventsTotal, "total events");
       SetCounterName(ECounter::EventsNeStsHits, "events discarded by N STS hits");
       SetCounterName(ECounter::EventsNeTofHits, "events discarded by N TOF hits");
       SetCounterName(ECounter::EventsNeBmonHits, "events discarded by N BMon hits");
       SetCounterName(ECounter::EventsNeTracks, "events discarded by N tracks");
+      SetCounterName(ECounter::LambdaCandidates, "potential lambda candidates");
       SetCounterName(ECounter::EventsSelected, "selected events");
 
       SetTimerName(ETimer::EventReconstruction, "event reconstruction");
@@ -67,6 +71,8 @@ namespace cbm::algo::evselect
       SetTimerName(ETimer::TrdHitFinder, "hit finding in TRD");
       SetTimerName(ETimer::TrackFinder, "track finding");
       SetTimerName(ETimer::V0Finder, "V0 finding");
+
+      SetRatioKeys({ECounter::Timeslices});
     }
 
    private:
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index f312ebaaa..0f192df1b 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -45,10 +45,6 @@
 
 #include <xpu/host.h>
 
-// DEBUG: BEGIN
-#include <set>
-// DEBUG: END
-
 using namespace cbm::algo;
 using fles::Subsystem;
 
@@ -427,16 +423,16 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
 
     // --- Reconstruct and select digi events
     if (fbReconstructDigiEvents) {
-      size_t nDiscardedEvents{0};
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::Timeslices);
       fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsTotal, events.size());
-      for (const auto& event : events) {
+      for (auto& event : events) {
         fEvSelectingMonitor.StartTimer(evselect::ETimer::EventReconstruction);
-        if (!ReconstructEvent(event)) {
-          ++nDiscardedEvents;
-        }
+        event.fSelectionTriggers = ReconstructEvent(event);
         fEvSelectingMonitor.StopTimer(evselect::ETimer::EventReconstruction);
       }
-      L_(info) << "Rate of discarded events " << double(nDiscardedEvents) / events.size();
+      auto v0FinderMonitor = fV0Finder->GetMonitor();
+      fEvSelectingMonitor.IncrementCounter(evselect::ECounter::LambdaCandidates,
+                                           v0FinderMonitor.GetCounterValue(kfp::ECounter::KfpLambdaCandidates));
     }
 
     // --- Filter data for output
@@ -525,8 +521,9 @@ void Reco::PrintTimings(xpu::timings& timings)
   }
 }
 
-bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
+CbmEventTriggers Reco::ReconstructEvent(const DigiEvent& digiEvent)
 {
+  CbmEventTriggers triggers(0);
   RecoResults recoEvent;
   //* BMON hit reconstruction
   {
@@ -550,7 +547,7 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
     fEvSelectingMonitor.StopTimer(evselect::ETimer::StsHitFinder);
     if (stsResults.hits.NElements() < 4) {  // TODO: Provide a config for cuts (testing mode for now)
       fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeStsHits);
-      return false;
+      return triggers;
     }
     recoEvent.stsHits = stsResults.hits;
   }
@@ -563,7 +560,7 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
     fEvSelectingMonitor.StopTimer(evselect::ETimer::TofHitFinder);
     if (hits.NElements() < 2) {  // TODO: Provide a config for cuts (testing mode for now)
       fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTofHits);
-      return false;
+      return triggers;
     }
     recoEvent.tofHits = std::move(hits);
   }
@@ -597,18 +594,19 @@ bool Reco::ReconstructEvent(const DigiEvent& digiEvent)
     fEvSelectingMonitor.StopTimer(evselect::ETimer::TrackFinder);
     if (recoEvent.tracks.size() < 2) {  // Reject all events with less then two tracks
       fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsNeTracks);
-      return false;
+      return triggers;
     }
   }
 
   //* V0-selector
   fEvSelectingMonitor.StartTimer(evselect::ETimer::V0Finder);
-  auto triggers = fV0Finder->ProcessEvent(recoEvent);
+  triggers = fV0Finder->ProcessEvent(recoEvent);
+  if (triggers.Test(CbmEventTriggers::ETrigger::Lambda)) {
+    L_(info) << "!!! Found event with potential lambda candidates";
+    fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
+  }
   fEvSelectingMonitor.StopTimer(evselect::ETimer::V0Finder);
-
-
-  fEvSelectingMonitor.IncrementCounter(evselect::ECounter::EventsSelected);
-  return true;
+  return triggers;
 }
 
 
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index eb8ba804a..35cb5c346 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -11,6 +11,8 @@
 #include <xpu/host.h>
 
 // fwd declarations
+class CbmEventTriggers;
+
 namespace fles
 {
   class Timeslice;
@@ -133,7 +135,7 @@ namespace cbm::algo
     void Init(const Options&);
     RecoResults Run(const fles::Timeslice&);
 
-    bool ReconstructEvent(const DigiEvent& event);
+    CbmEventTriggers ReconstructEvent(const DigiEvent& event);
     void Finalize();
     void PrintTimings(xpu::timings&);
 
diff --git a/algo/kfp/KfpV0FinderChain.cxx b/algo/kfp/KfpV0FinderChain.cxx
index e23bc9e81..96dc60a59 100644
--- a/algo/kfp/KfpV0FinderChain.cxx
+++ b/algo/kfp/KfpV0FinderChain.cxx
@@ -27,7 +27,21 @@ V0FinderChain::V0FinderChain(const std::unique_ptr<qa::Manager>& qaManager)
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void V0FinderChain::Finalize() { L_(info) << fMonitorRun.ToString(); }
+void V0FinderChain::Finalize()
+{
+  GetMonitor();  // A hack to update the run monitor
+  L_(info) << fMonitorRun.ToString();
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+cbm::algo::kfp::V0FinderMonitorData_t V0FinderChain::GetMonitor()
+{
+  cbm::algo::kfp::V0FinderMonitorData_t monitorData = fMonitorTimeslice;
+  fMonitorTimeslice.Reset();
+  fMonitorRun.AddMonitorData(monitorData);
+  return monitorData;
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
@@ -76,8 +90,6 @@ try {
   }
   const auto& pion{particles[iPion]};
   const auto& proton{particles[iProton]};
-  L_(info) << "!!!!!!!!!!! > pion:   " << pion.minDca;
-  L_(info) << "!!!!!!!!!!! > proton: " << proton.minDca;
 
   // ----- Define a BMON diamond
   if (fBmonDefinedAddresses.empty()) {
@@ -139,12 +151,10 @@ catch (const std::exception& err) {
 //
 V0FinderChain::EventOutput V0FinderChain::ProcessEvent(const RecoResults& recoEvent)
 {
-  EventOutput res;
-  res.triggers = fFinder.Process(recoEvent);
-  res.monitor  = fFinder.GetEventMonitor();
+  EventOutput res = fFinder.Process(recoEvent);
   if (fpFinderQa != nullptr) {
     fpFinderQa->Exec(recoEvent, fFinder);
   }
-  fMonitorRun.AddMonitorData(res.monitor);
+  fMonitorTimeslice.AddMonitorData(fFinder.GetEventMonitor());
   return res;
 }
diff --git a/algo/kfp/KfpV0FinderChain.h b/algo/kfp/KfpV0FinderChain.h
index 8053c93f7..95f10ebba 100644
--- a/algo/kfp/KfpV0FinderChain.h
+++ b/algo/kfp/KfpV0FinderChain.h
@@ -31,12 +31,7 @@ namespace cbm::algo
   /// \brief A chain for the V0 finder
   class V0FinderChain : public SubChain {
    public:
-    /// \struct EventOutput
-    /// \brief  Output from the event
-    struct EventOutput {
-      CbmEventTriggers triggers;           ///< Set of triggers
-      kfp::V0FinderMonitorData_t monitor;  ///< Monitor
-    };
+    using EventOutput = CbmEventTriggers;
 
     /// \brief Default constructor
     V0FinderChain() = default;
@@ -63,6 +58,9 @@ namespace cbm::algo
     /// \brief Finalizes the instance (called in the end of the run)
     void Finalize();
 
+    /// \brief Gets a monitor
+    kfp::V0FinderMonitorData_t GetMonitor();
+
     /// \brief Sets BMON diamond addresses array
     /// \note  The addresses must be taken from bmon::Hitfind::GetDiamondAddresses()
     void SetBmonDefinedAddresses(const PODVector<uint32_t>& addresses) { fBmonDefinedAddresses = addresses; }
@@ -76,6 +74,7 @@ namespace cbm::algo
    private:
     kfp::V0Finder fFinder;                                 ///< Instance of the V0-finding algorithm
     kfp::V0FinderMonitor fMonitorRun;                      ///< Monitor per run
+    kfp::V0FinderMonitorData_t fMonitorTimeslice;          ///< Monitor per timeslice
     std::unique_ptr<kfp::V0FinderQa> fpFinderQa{nullptr};  ///< QA module
     PODVector<uint32_t> fBmonDefinedAddresses;             ///< Available addresses of BMON
   };
diff --git a/algo/kfp/KfpV0FinderMonitor.h b/algo/kfp/KfpV0FinderMonitor.h
index 5bb4311b6..d60d5b135 100644
--- a/algo/kfp/KfpV0FinderMonitor.h
+++ b/algo/kfp/KfpV0FinderMonitor.h
@@ -92,6 +92,8 @@ namespace cbm::algo::kfp
       SetTimerName(ETimer::PreselectTracks, "Track preselection");
       SetTimerName(ETimer::InitKfp, "KFParticleFinder initialization");
       SetTimerName(ETimer::ExecKfp, "KFParticleFinder execution");
+
+      SetRatioKeys({ECounter::EventsTotal, ECounter::TracksTotal, ECounter::TracksSelected});
     }
 
    private:
diff --git a/core/data/CbmEventTriggers.h b/core/data/CbmEventTriggers.h
index 841d86bd4..a2c252df5 100644
--- a/core/data/CbmEventTriggers.h
+++ b/core/data/CbmEventTriggers.h
@@ -16,24 +16,30 @@
 #include <Rtypes.h>  // for ClassDef
 #endif
 
+#include <boost/serialization/access.hpp>
+
 /// \class CbmEventTriggers
 /// \brief Class to store different triggers for a given event
 class CbmEventTriggers {
  public:
   /// \enum   ETrigger
-  /// \brief  Defines a trigger address
-  enum class ETrigger : uint8_t
+  /// \brief  Defines a trigger bitmask
+  enum class ETrigger : uint32_t
   {
-    Lambda = 0b00000001,  ///< Lambda-trigger
-    Ks     = 0b00000010   ///< Ks-trigger
+    Lambda = 0x00000001,  ///< Lambda-trigger
+    Ks     = 0x00000002   ///< Ks-trigger
   };
 
   //using Trigger_t = std::underlying_type_t<ETrigger>;
-  using Trigger_t = uint8_t;
+  using Trigger_t = uint32_t;
 
   /// \brief Default constructor
   CbmEventTriggers() = default;
 
+  /// \brief A constructor from integer
+  /// \param bitmap  A bitmap of the triggers
+  explicit CbmEventTriggers(uint32_t bitmap) { fBitmap = bitmap; }
+
   /// \brief Copy constructor
   CbmEventTriggers(const CbmEventTriggers&) = default;
 
@@ -49,33 +55,47 @@ class CbmEventTriggers {
   /// \brief Move assignment operator
   CbmEventTriggers& operator=(CbmEventTriggers&&) = default;
 
+  /// \brief Gets a bitmap
+  /// \return bitmap  (integer)
+  Trigger_t GetBitmap() const { return fBitmap; }
+
   /// \brief Sets a trigger
   /// \param key  Trigger key
-  void Set(ETrigger key) { fTriggers |= static_cast<Trigger_t>(key); }
+  void Set(ETrigger key) { fBitmap |= static_cast<Trigger_t>(key); }
 
   /// \brief Resets a trigger
   /// \param key  Trigger key
-  void Reset(ETrigger key) { fTriggers &= ~static_cast<Trigger_t>(key); }
+  void Reset(ETrigger key) { fBitmap &= ~static_cast<Trigger_t>(key); }
+
+  /// \brief Resets all the triggers
+  void ResetAll() { fBitmap = 0; }
 
   /// \brief Tests a particular single trigger
   /// \param key  Trigger key
-  bool Test(ETrigger key) const { return static_cast<bool>(fTriggers & static_cast<Trigger_t>(key)); }
+  bool Test(ETrigger key) const { return static_cast<bool>(fBitmap & static_cast<Trigger_t>(key)); }
 
-  /// \brief Tests, if ALL the triggers in the mask are on
-  /// \param mask  Trigger mask
-  bool TestAll(Trigger_t mask) const { return mask == (fTriggers & mask); }
+  /// \brief Tests, if ALL the triggers in the bitmask are on
+  /// \param bitmask  Trigger bitmask
+  bool TestAll(Trigger_t bitmask) const { return bitmask == (fBitmap & bitmask); }
 
-  /// \brief Tests, if ANY of the triggers in the mask are on
-  /// \param mask  Trigger mask
-  bool TestAny(Trigger_t mask) const { return static_cast<bool>(fTriggers | mask); }
+  /// \brief Tests, if ANY of the triggers in the bitmask are on
+  /// \param bitmask  Trigger bitmask
+  bool TestAny(Trigger_t bitmask) const { return static_cast<bool>(fBitmap | bitmask); }
 
   /// \brief String representation of the class content
   std::string ToString() const;
 
  private:
-  Trigger_t fTriggers{0};
+  Trigger_t fBitmap{0};  ///< bitmap storing the triggers according to ETrigger
+
+  friend class boost::serialization::access;
+  template<class Archive>
+  void serialize(Archive& ar, const unsigned int /*version*/)
+  {
+    ar& fBitmap;
+  }
 
 #if !defined(NO_ROOT) && !XPU_IS_HIP_CUDA
-  ClassDefNV(CbmEventTriggers, 1);
+  ClassDefNV(CbmEventTriggers, 2);
 #endif
 };
diff --git a/core/data/global/CbmDigiEvent.h b/core/data/global/CbmDigiEvent.h
index 242740e57..ec90985bb 100644
--- a/core/data/global/CbmDigiEvent.h
+++ b/core/data/global/CbmDigiEvent.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021-22 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2021-25 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Volker Friese [committer] */
 
@@ -6,6 +6,7 @@
 #define CBMDIGIEVENT_H 1
 
 #include "CbmDigiData.h"
+#include "CbmEventTriggers.h"
 
 #include <boost/serialization/access.hpp>
 
@@ -14,29 +15,32 @@
  ** @brief Collection of digis from all detector systems within one event
  ** @author Volker Friese <v.friese@gsi.de>
  ** @since 7.12.2022
- ** @version 2.0
+ ** @version 3.0
  **/
 class CbmDigiEvent {
 
 public:
-  CbmDigiData fData;  ///< Event data
-  uint64_t fNumber;   ///< Event identifier
-  double fTime;       ///< Event trigger time [ns]
-
-
-  friend class boost::serialization::access;
-  /** @brief BOOST serializer**/
-  template<class Archive>
-  void serialize(Archive& ar, const unsigned int /*version*/)
-  {
-    ar& fData;
-    ar& fNumber;
-    ar& fTime;
-  }
+ CbmDigiData fData;                    ///< Event data
+ uint64_t fNumber;                     ///< Event identifier
+ double fTime;                         ///< Event trigger time [ns]
+ CbmEventTriggers fSelectionTriggers;  ///< Event selection triggers
+
+ friend class boost::serialization::access;
+ /** @brief BOOST serializer**/
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int version)
+ {
+   ar& fData;
+   ar& fNumber;
+   ar& fTime;
+   if (version >= 3) {
+     ar& fSelectionTriggers;
+   }
+ }
 
   // --- ROOT serializer
 #ifndef NO_ROOT
-  ClassDefNV(CbmDigiEvent, 2);
+ ClassDefNV(CbmDigiEvent, 3);
 #endif
 
   /** @brief Clear content **/
@@ -45,6 +49,7 @@ public:
     fData.Clear();
     fNumber = 0;
     fTime   = 0.;
+    fSelectionTriggers.ResetAll();
   }
 };
 
-- 
GitLab


From 3614fb4ed51db87889e57c87cdfc4ac8b1ad8540 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Sat, 15 Feb 2025 10:03:44 +0100
Subject: [PATCH 17/17] Removing ROOT library linking to
 CbmKFParticleOnlineInterface

---
 algo/kfp/interface/CMakeLists.txt | 17 +++--------------
 1 file changed, 3 insertions(+), 14 deletions(-)

diff --git a/algo/kfp/interface/CMakeLists.txt b/algo/kfp/interface/CMakeLists.txt
index c1eaadf76..4beacabb1 100644
--- a/algo/kfp/interface/CMakeLists.txt
+++ b/algo/kfp/interface/CMakeLists.txt
@@ -1,25 +1,14 @@
 if(NOT CBM_ONLINE_STANDALONE)
   ### CbmKFParticleOnlineInterface
+  # Just reusing the KFParticle library, compiled in the external
   add_library(CbmKFParticleOnlineInterface INTERFACE)
   target_include_directories(CbmKFParticleOnlineInterface INTERFACE)
   target_compile_definitions(CbmKFParticleOnlineInterface 
     INTERFACE DO_TPCCATRACKER_EFF_PERFORMANCE NonhomogeneousField CBM USE_TIMERS)
-  target_link_libraries(CbmKFParticleOnlineInterface 
-              INTERFACE KFParticle
-                        ROOT::Core 
-                        ROOT::Hist 
-                        ROOT::MathCore
-                        ROOT::Gpad
-                        ROOT::Graf
-                        ROOT::Physics
-                        ROOT::EG
-                        ROOT::RIO
-                        ROOT::Tree
-                        ROOT::MathCore
-                        )
+  target_link_libraries(CbmKFParticleOnlineInterface INTERFACE KFParticle)
   install(TARGETS CbmKFParticleOnlineInterface DESTINATION lib)
 else()
-  # Creating a replacement of the CbmKFParticleOnlineInterface library for a standalone mode
+  # Creating a replacement of the KFParticle library for a standalone mode
   set(KFP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../external/KFParticle/KFParticle)
 
   set(SRCS
-- 
GitLab