From a4734cb8d3c05a926cd2702ecde44162cd2fed5c 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 1/4] online: BMON digi calibrator --- algo/CMakeLists.txt | 1 + algo/detectors/bmon/Calibrate.cxx | 166 +++++++++++++++++++++++++++ algo/detectors/bmon/Calibrate.h | 50 ++++++++ algo/detectors/bmon/CalibrateSetup.h | 67 +++++++++++ algo/detectors/bmon/UnpackMS.cxx | 1 + algo/global/ParFiles.cxx | 12 +- algo/global/ParFiles.h | 2 + algo/global/Reco.cxx | 71 ++++++++++++ algo/global/Reco.h | 2 + core/data/bmon/CbmBmonDigi.h | 27 ++--- core/data/tof/CbmTofAddress.cxx | 18 +++ core/data/tof/CbmTofAddress.h | 13 ++- core/data/tof/CbmTofDigi.h | 29 ++--- external/InstallParameter.cmake | 2 + 14 files changed, 431 insertions(+), 30 deletions(-) create mode 100644 algo/detectors/bmon/Calibrate.cxx create mode 100644 algo/detectors/bmon/Calibrate.h create mode 100644 algo/detectors/bmon/CalibrateSetup.h diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt index 09ee075af5..58ac4397e4 100644 --- a/algo/CMakeLists.txt +++ b/algo/CMakeLists.txt @@ -95,6 +95,7 @@ set(SRCS evselector/DigiEventSelector.cxx evselector/DigiEventSelectorConfig.cxx unpack/CommonUnpacker.cxx + detectors/bmon/Calibrate.cxx detectors/sts/ChannelMaskSet.cxx detectors/sts/ReadoutConfig.cxx detectors/sts/HitfinderChain.cxx diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx new file mode 100644 index 0000000000..70d99daeb4 --- /dev/null +++ b/algo/detectors/bmon/Calibrate.cxx @@ -0,0 +1,166 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Calibrate.h +/// \brief Calibrator for the BMON digis (implementation) +/// \since 04.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#include "Calibrate.h" + +#include "AlgoFairloggerCompat.h" +#include "CbmTofAddress.h" +#include "util/TimingsFormat.h" + +#include <chrono> + +using cbm::algo::bmon::Calibrate; +using cbm::algo::bmon::CalibrateSetup; +using fles::Subsystem; + +// --------------------------------------------------------------------------------------------------------------------- +// +Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSmOffset(1, 0), fRpcOffset(1, 0) +{ + // Initialize offset arrays for channel deadtime check + int32_t NbSm = fSetup.NbSm; + int32_t NbRpc = fSetup.NbRpc; + for (int32_t Sm = 0; Sm < NbSm; Sm++) { + fSmOffset.push_back(fSmOffset.back() + NbRpc); + for (int32_t Rpc = 0; Rpc < NbRpc; Rpc++) { + int32_t NbChan = fSetup.rpcs[Sm * NbRpc + Rpc].chanPar.size(); + fRpcOffset.push_back(fRpcOffset.back() + 2 * NbChan); //Factor 2 for channel sides + } + } + + // **** DEBUG: BEGIN + for (size_t iO = 0; iO < fSmOffset.size(); ++iO) { + L_(info) << "SM offset: " << iO << ", " << fSmOffset[iO]; + } + for (size_t iO = 0; iO < fRpcOffset.size(); ++iO) { + L_(info) << "RPC offset: " << iO << ", " << fRpcOffset[iO]; + } + // **** DEBUG: END + + fChannelDeadTime = std::vector<double>(fRpcOffset.back(), std::numeric_limits<double>::quiet_NaN()); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn) +{ + xpu::push_timer("TofCalibrate"); + xpu::t_add_bytes(digiIn.size_bytes()); + + // --- Output data + resultType result = {}; + + auto& calDigiOut = result.first; + auto& monitor = result.second; + calDigiOut.reserve(digiIn.size()); + + std::fill(fChannelDeadTime.begin(), fChannelDeadTime.end(), std::numeric_limits<double>::quiet_NaN()); + + for (const auto& digi : digiIn) { + uint32_t address = static_cast<uint32_t>(digi.GetAddress()); + const int32_t SmType{CbmTofAddress::GetSmType(address)}; + const int32_t Sm{CbmTofAddress::GetSmId(address)}; + const int32_t Rpc{CbmTofAddress::GetRpcId(address)}; + const int NbRpc{fSetup.NbRpc}; + + int32_t Chan{CbmTofAddress::GetChannelId(address)}; + int32_t Side{CbmTofAddress::GetChannelSide(address)}; + + // NOTE: Follow the hack in the CbmTofEventClusterizer::Exec() for the BMON digis and change the address + // of calibrated digis. In general it is incorrect and needs further investigation. + uint32_t calAddress = address; + if (Side == 1) { + Side = 0; + Chan += 6; + calAddress = CbmTofAddress::GetUniqueAddress(Sm, Rpc, Chan, Side, SmType); + } + + auto& rpcs = fSetup.rpcs; + if (SmType != fSetup.SmType || Sm * NbRpc + Rpc >= static_cast<int>(rpcs.size())) { + monitor.fDigiCalibUnknownRPC++; + L_(error) << "Unknown BMON digi address: " << CbmTofAddress::ToString(calAddress); + continue; + } + + CalibrateSetup::Rpc& rpcPar = fSetup.rpcs[Sm * NbRpc + Rpc]; + CalibrateSetup::Channel& chanPar = rpcPar.chanPar[Chan]; + + // Check dead time + // FIXME: BMON digis (must) have only one side -> Fix the offsets, when mcbm2024_05 will be fixed + const size_t chanIdx = fRpcOffset[fSmOffset[Sm] + Rpc] + Chan + Side * rpcPar.chanPar.size(); + const double deadTime = fChannelDeadTime[chanIdx]; + + if (!std::isnan(deadTime) && digi.GetTime() <= deadTime) { + fChannelDeadTime[chanIdx] = digi.GetTime() + rpcPar.channelDeadtime; + monitor.fDigiDeadTimeCount++; + continue; + } + fChannelDeadTime[chanIdx] = digi.GetTime() + rpcPar.channelDeadtime; + + // Create calibrated digi + CbmBmonDigi& pCalDigi = calDigiOut.emplace_back(digi); + pCalDigi.SetAddress(calAddress); + + // Check if channel sides need to be swapped + if (rpcPar.swapChannelSides) { + pCalDigi.SetAddress(CbmTofAddress::GetUniqueAddress(Sm, Rpc, Chan, (0 == Side) ? 1 : 0, SmType)); + } + + // calibrate Digi Time + pCalDigi.SetTime(pCalDigi.GetTime() - chanPar.vCPTOff[Side]); + + // subtract Offset + const double dTot = std::max(pCalDigi.GetCharge() - chanPar.vCPTotOff[Side], 0.001); + + // calibrate Digi charge (corresponds to TOF ToT) + pCalDigi.SetCharge(dTot * chanPar.vCPTotGain[Side]); + + // walk correction + std::vector<double>& walk = chanPar.vCPWalk[Side]; + const double dTotBinSize = (rpcPar.TOTMax - rpcPar.TOTMin) / rpcPar.numClWalkBinX; + int32_t iWx = std::max((int32_t)((pCalDigi.GetCharge() - rpcPar.TOTMin) / dTotBinSize), 0); + iWx = std::min(iWx, rpcPar.numClWalkBinX - 1); + + const double dDTot = (pCalDigi.GetCharge() - rpcPar.TOTMin) / dTotBinSize - (double) iWx - 0.5; + double dWT = walk[iWx]; + + // linear interpolation to next bin + if (dDTot > 0) { + if (iWx < rpcPar.numClWalkBinX - 1) { + dWT += dDTot * (walk[iWx + 1] - walk[iWx]); + } + } + else { + if (0 < iWx) { + dWT -= dDTot * (walk[iWx - 1] - walk[iWx]); + } + } + pCalDigi.SetTime(pCalDigi.GetTime() - dWT); // calibrate Digi Time + } + + /// Sort the buffers of hits due to the time offsets applied + // (insert-sort faster than std::sort due to pre-sorting) + for (std::size_t i = 1; i < calDigiOut.size(); ++i) { + CbmTofDigi digi = calDigiOut[i]; + std::size_t j = i; + while (j > 0 && calDigiOut[j - 1].GetTime() > digi.GetTime()) { + calDigiOut[j] = calDigiOut[j - 1]; + --j; + } + calDigiOut[j] = digi; + } + + // Kept for possible unsorted input + // std::sort(calDigiOut.begin(), calDigiOut.end(), + // [](const CbmTofDigi& a, const CbmTofDigi& b) -> bool { return a.GetTime() < b.GetTime(); }); + + monitor.fTime = xpu::pop_timer(); + monitor.fNumDigis = digiIn.size(); + return result; +} diff --git a/algo/detectors/bmon/Calibrate.h b/algo/detectors/bmon/Calibrate.h new file mode 100644 index 0000000000..291a6882b4 --- /dev/null +++ b/algo/detectors/bmon/Calibrate.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Calibrate.h +/// \brief Calibratior for the BMON digis +/// \since 04.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "CbmBmonDigi.h" +#include "PartitionedVector.h" +#include "bmon/CalibrateSetup.h" +#include "tof/Calibrate.h" // for the monitor data +#include "tof/Clusterizer.h" + +#include <gsl/span> +#include <optional> +#include <sstream> +#include <vector> + +#include <xpu/host.h> + +namespace cbm::algo::bmon +{ + // NOTE: reusing TOF monitor + using CalibrateMonitorData = tof::CalibrateMonitorData; + + /// \class Calibrate + /// \brief Algorithm to calibrate BMon digis + class Calibrate { + public: + using resultType = std::pair<std::vector<CbmBmonDigi>, CalibrateMonitorData>; + + /// \brief Constructor + /// \param params Calibration parameters + explicit Calibrate(CalibrateSetup params); + + /// \brief Calibrates a portion of digis + /// \param digiIn A portion of digis to calibrate + resultType operator()(gsl::span<const CbmBmonDigi> digiIn); + + private: + CalibrateSetup fSetup; ///< Parameters of calibrator + std::vector<double> fChannelDeadTime; ///< Storage for dead time check + std::vector<size_t> fSmOffset; ///< Super module offset + std::vector<size_t> fRpcOffset; ///< RPC offset + }; +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/CalibrateSetup.h b/algo/detectors/bmon/CalibrateSetup.h new file mode 100644 index 0000000000..0d9dedd93c --- /dev/null +++ b/algo/detectors/bmon/CalibrateSetup.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file CalibrateSetup.h +/// \brief Configuration of the calibrator for the BMON digis +/// \since 04.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "Definitions.h" +#include "yaml/Property.h" + +#include <array> +#include <map> +#include <string> +#include <vector> + +namespace cbm::algo::bmon +{ + /// \struct CalibrateSetup + /// \brief BMON calibration per channel + struct CalibrateSetup { + + struct Channel { + std::vector<double> vCPTOff; + std::vector<double> vCPTotGain; + std::vector<double> vCPTotOff; + std::vector<std::vector<double>> vCPWalk; + + CBM_YAML_PROPERTIES(yaml::Property(&Channel::vCPTOff, "vCPTOff", "CPT offset"), + yaml::Property(&Channel::vCPTotGain, "vCPTotGain", "CP time over threshold gain"), + yaml::Property(&Channel::vCPTotOff, "vCPTotOff", "CP time over threshold offset"), + yaml::Property(&Channel::vCPWalk, "vCPWalk", "CP walk correction", YAML::Block, YAML::Flow)); + }; + + struct Rpc { + int32_t numClWalkBinX; + double TOTMax; + double TOTMin; + bool swapChannelSides; + double channelDeadtime; + std::vector<Channel> chanPar; + + CBM_YAML_PROPERTIES(yaml::Property(&Rpc::numClWalkBinX, "numClWalkBinX", "number of walk correction bins"), + yaml::Property(&Rpc::TOTMax, "TOTMax", "maximum time over threshold"), + yaml::Property(&Rpc::TOTMin, "TOTMin", "minimum time over threshold"), + yaml::Property(&Rpc::swapChannelSides, "swapChannelSides", "flag for swapping channel sides"), + yaml::Property(&Rpc::channelDeadtime, "channelDeadtime", "channel dead time"), + yaml::Property(&Rpc::chanPar, "chanPar", "channel parameters")); + }; + + /* Members */ + int32_t SmType; + int32_t NbSm; + int32_t NbRpc; + std::vector<Rpc> rpcs; + + CBM_YAML_PROPERTIES( + yaml::Property(&CalibrateSetup::SmType, "SmType", "Super module type"), + yaml::Property(&CalibrateSetup::NbSm, "NbSm", "Number of SMs"), + yaml::Property(&CalibrateSetup::NbRpc, "NbRpc", "Number of RPCs"), + yaml::Property(&CalibrateSetup::rpcs, "rpcs", "Parameters of RPCs")); + }; + +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/UnpackMS.cxx b/algo/detectors/bmon/UnpackMS.cxx index b4fbf123e1..22d56e90d1 100644 --- a/algo/detectors/bmon/UnpackMS.cxx +++ b/algo/detectors/bmon/UnpackMS.cxx @@ -5,6 +5,7 @@ #include "UnpackMS.h" #include "AlgoFairloggerCompat.h" +#include "CbmTofAddress.h" #include <cassert> #include <cmath> diff --git a/algo/global/ParFiles.cxx b/algo/global/ParFiles.cxx index 8eb07742a6..eabec37b4b 100644 --- a/algo/global/ParFiles.cxx +++ b/algo/global/ParFiles.cxx @@ -25,7 +25,9 @@ ParFiles::ParFiles(uint32_t runId) switch (setup) { case Setup::mCBM2022: - bmon.readout = "BmonReadout_mcbm2022.yaml"; + bmon.readout = "BmonReadout_mcbm2022.yaml"; + bmon.calibrate = "BmonCalibratePar_mcbm2022.yaml"; + bmon.hitfinder = "BmonHitfinderPar_mcbm2022.yaml"; sts.readout = "StsReadout_mcbm2022.yaml"; sts.chanMask = "StsChannelMaskSet_mcbm2022.yaml"; @@ -46,7 +48,9 @@ ParFiles::ParFiles(uint32_t runId) break; case Setup::mCBM2024_03: - bmon.readout = "BmonReadout_mcbm2024.yaml"; + bmon.readout = "BmonReadout_mcbm2024.yaml"; + bmon.calibrate = "BmonCalibratePar_mcbm2024.yaml"; + bmon.hitfinder = "BmonHitfinderPar_mcbm2024.yaml"; sts.readout = "StsReadout_mcbm2024.yaml"; sts.chanMask = "StsChannelMaskSet_mcbm2024.yaml"; @@ -67,7 +71,9 @@ ParFiles::ParFiles(uint32_t runId) break; case Setup::mCBM2024_05: - bmon.readout = "BmonReadout_mcbm2024.yaml"; + bmon.readout = "BmonReadout_mcbm2024.yaml"; + bmon.calibrate = "mcbm2024_05/BmonCalibratePar.yaml"; + bmon.hitfinder = "mcbm2024_05/BmonHitfinderPar.yaml"; sts.readout = "StsReadout_mcbm2024.yaml"; sts.chanMask = "StsChannelMaskSet_mcbm2024.yaml"; diff --git a/algo/global/ParFiles.h b/algo/global/ParFiles.h index 9c966ac637..691e27b39b 100644 --- a/algo/global/ParFiles.h +++ b/algo/global/ParFiles.h @@ -26,6 +26,8 @@ namespace cbm::algo struct { fs::path readout; + fs::path calibrate; + fs::path hitfinder; } bmon; struct { diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx index e9a0a7a9a0..9bdc35bb6b 100644 --- a/algo/global/Reco.cxx +++ b/algo/global/Reco.cxx @@ -14,6 +14,7 @@ #include "RecoGeneralQa.h" #include "StsDigiQa.h" #include "TrackingSetup.h" +#include "bmon/Calibrate.h" #include "bmon/ReadoutConfig.h" #include "bmon/Unpack.h" #include "ca/TrackingChain.h" @@ -41,6 +42,10 @@ #include <xpu/host.h> +// DEBUG: BEGIN +#include <set> +// DEBUG: END + using namespace cbm::algo; using fles::Subsystem; @@ -204,6 +209,14 @@ 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); + } // TOF Hitfinder if (Opts().Has(fles::Subsystem::TOF) && Opts().Has(Step::LocalReco)) { @@ -372,6 +385,64 @@ RecoResults Reco::Run(const fles::Timeslice& ts) QueueEvbuildMetrics(evbuildMonitor); } + // ***** DEBUG: BEGIN + if constexpr (1) { + std::set<uint32_t> bmonFoundAddressesUnp; // found addresses among unpacked bmon digis + std::set<uint32_t> bmonFoundAddressesCal; // found addresses among clalibrated bmon digis + + int nEvents = 20; //events.size(); + for (int iE = 0; iE < nEvents; ++iE) { + //L_(info) << "-------------- event #" << iE; + const auto& event = events[iE]; + for (const auto& digi : event.fBmon) { + bmonFoundAddressesUnp.insert(digi.GetAddress()); + } + + // Calibrate TOF digis: + auto [tofDigis, tofCalMonitor] = (*fTofCalibrator)(event.fTof); + auto [bmonDigis, bmonCalMonitor] = (*fBmonCalibrator)(event.fBmon); + + for (const auto& digi : bmonDigis) { + bmonFoundAddressesCal.insert(digi.GetAddress()); + } + + double bmonMinT = 1e+20; + double bmonMaxT = -1e+20; + for (const auto& digi : bmonDigis) { + //L_(info) << "BMON: " << digi.GetTime(); + bmonMinT = std::min(bmonMinT, digi.GetTime()); + bmonMaxT = std::max(bmonMaxT, digi.GetTime()); + } + double tofMinT = 1e+20; + double tofMaxT = -1e+20; + for (const auto& digi : tofDigis) { + //L_(info) << "TOF: " << digi.GetTime(); + tofMinT = std::min(tofMinT, digi.GetTime()); + tofMaxT = std::max(tofMaxT, digi.GetTime()); + } + double stsMinT = 1e+20; + double stsMaxT = -1e+20; + const auto& stsDigis = event.fSts; + for (const auto& digi : stsDigis) { + //L_(info) << "STS: " << digi.GetTime(); + stsMinT = std::min(stsMinT, digi.GetTime()); + stsMaxT = std::max(stsMaxT, digi.GetTime()); + } + L_(info) << "BMON time: " << bmonMinT << ", " << bmonMaxT << ", " << (bmonMaxT - bmonMinT); + L_(info) << "TOF time: " << tofMinT << ", " << tofMaxT << ", " << (tofMaxT - tofMinT); + L_(info) << "STS time: " << stsMinT << ", " << stsMaxT << ", " << (stsMaxT - stsMinT); + } + L_(info) << "!!!! Found addresses in BMON after unpacking: "; + for (auto address : bmonFoundAddressesUnp) { + L_(info) << " - " << CbmTofAddress::ToString(address); + } + L_(info) << "!!!! Found addresses in BMON after calibration: "; + for (auto address : bmonFoundAddressesCal) { + L_(info) << " - " << CbmTofAddress::ToString(address); + } + } + // ***** DEBUG: END + // --- Filter data for output if (Opts().HasOutput(RecoData::DigiTimeslice)) { results.bmonDigis = std::move(digis.fBmon); diff --git a/algo/global/Reco.h b/algo/global/Reco.h index 8e26d35271..982a95cfa4 100644 --- a/algo/global/Reco.h +++ b/algo/global/Reco.h @@ -27,6 +27,7 @@ namespace cbm::algo namespace bmon { class Unpack; + class Calibrate; } namespace much @@ -146,6 +147,7 @@ namespace cbm::algo // BMON std::unique_ptr<bmon::Unpack> fBmonUnpack; + std::unique_ptr<bmon::Calibrate> fBmonCalibrator; // MUCH std::unique_ptr<much::Unpack> fMuchUnpack; diff --git a/core/data/bmon/CbmBmonDigi.h b/core/data/bmon/CbmBmonDigi.h index 200e72d0ce..7f0584e2fc 100644 --- a/core/data/bmon/CbmBmonDigi.h +++ b/core/data/bmon/CbmBmonDigi.h @@ -111,19 +111,20 @@ public: private: - int32_t fAddress = ToIntegralType<ECbmModuleId>(ECbmModuleId::kBmon); ///< Unique CBM address - double fTime = -1.; ///< Time of signal in BMON [ns] - float fCharge = -1.; ///< Charge - - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive& ar, const unsigned int /*version*/) - { - ar& fAddress; - ar& fTime; - ar& fCharge; - } + // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t + int32_t fAddress = ToIntegralType<ECbmModuleId>(ECbmModuleId::kBmon); ///< Unique CBM address + double fTime = -1.; ///< Time of signal in BMON [ns] + float fCharge = -1.; ///< Charge + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /*version*/) + { + ar& fAddress; + ar& fTime; + ar& fCharge; + } #ifndef NO_ROOT ClassDefNV(CbmBmonDigi, 1); diff --git a/core/data/tof/CbmTofAddress.cxx b/core/data/tof/CbmTofAddress.cxx index 0ed83293f2..068504c399 100644 --- a/core/data/tof/CbmTofAddress.cxx +++ b/core/data/tof/CbmTofAddress.cxx @@ -11,6 +11,9 @@ #include "CbmTofAddress.h" +#include <iomanip> +#include <sstream> + // It seems C++ standard force the initialization to be in cxx/cpp file (outside of class definition) // When not trivial constant values => To check if it should apply also to simple values maybe? /** Offset in bits for Super Module Id in the address field **/ @@ -40,3 +43,18 @@ const int32_t CbmTofAddress::fgkiStripFullIdMask = (((1 << fgkSystemBits) - 1)) + (((1 << fgkSmIdBits) - 1) << fgkSmIdOffset) + (((1 << fgkSmTypeBits) - 1) << fgkSmTypeOffset) + (((1 << fgkRpcIdBits) - 1) << fgkRpcIdOffset) + (((1 << fgkChannelIdBits) - 1) << fgkChannelIdOffset); + +std::string CbmTofAddress::ToString(int32_t address) +{ + using std::setfill; + using std::setw; + std::stringstream msg; + msg << std::hex << "0x" << setw(8) << setfill('0') << address << std::dec << setfill(' '); + msg << ": SmType=" << setw(3) << CbmTofAddress::GetSmType(address); + msg << ", Sm=" << setw(2) << CbmTofAddress::GetSmId(address); + msg << ", Rpc=" << setw(2) << CbmTofAddress::GetRpcId(address); + msg << ", Ch=" << setw(2) << CbmTofAddress::GetChannelId(address); + msg << ", Side=" << setw(1) << CbmTofAddress::GetChannelSide(address); + msg << ", RpcType=" << setw(2) << CbmTofAddress::GetRpcType(address); + return msg.str(); +} diff --git a/core/data/tof/CbmTofAddress.h b/core/data/tof/CbmTofAddress.h index f8954d532e..3abf9b1703 100644 --- a/core/data/tof/CbmTofAddress.h +++ b/core/data/tof/CbmTofAddress.h @@ -44,6 +44,7 @@ #include "CbmTofDetectorId_v12b.h" // for CbmTofDetectorId_v12b #include <cstdint> +#include <string> class CbmTofAddress : public CbmAddress { public: @@ -91,6 +92,11 @@ public: ** @return systemId **/ static int32_t GetRpcId(uint32_t address) { return ((address >> fgkRpcIdOffset) & ((1 << fgkRpcIdBits) - 1)); }; + /** Get the Rpc Type from the address + ** @param address Unique address + ** @return systemId + **/ + static int32_t GetRpcType(uint32_t address) { return ((address >> fgkRpcTypeOffset) & ((1 << fgkRpcTypeBits) - 1)); }; /** Get the Channel Id from the address ** @param address Unique address ** @return systemId @@ -169,8 +175,13 @@ public: return GetUniqueAddress(detId.GetSModule(detIdInput), detId.GetCounter(detIdInput), detId.GetCell(detIdInput), 0, detId.GetSMType(detIdInput)); }; + /** String representation of the address + ** @param address Unique address + ** @return String representation of the address + **/ + static std::string ToString(int32_t address); -private: + private: /** ** To adapt the address sub-fields repartition in size, ** you just need to change number of bits of the two sub-fields changing length. diff --git a/core/data/tof/CbmTofDigi.h b/core/data/tof/CbmTofDigi.h index 942c6edb18..7448ad1a72 100644 --- a/core/data/tof/CbmTofDigi.h +++ b/core/data/tof/CbmTofDigi.h @@ -109,6 +109,7 @@ public: /** ** @brief Inherited from CbmDigi. **/ + // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t int32_t GetAddress() const { return fuAddress; }; @@ -160,6 +161,8 @@ public: double GetSide() const { return CbmTofAddress::GetChannelSide(GetAddress()); }; /** Modifiers **/ + + // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t void SetAddress(int32_t address) { fuAddress = address; }; void SetAddress(uint32_t Sm, uint32_t Rpc, uint32_t Channel, uint32_t Side = 0, uint32_t SmType = 0); void SetTime(double time) { fdTime = time; }; @@ -169,19 +172,19 @@ public: private: - double fdTime; ///< Absolute time [ps] - double fdTot; ///< Tot [ps] - uint32_t fuAddress; ///< Unique channel address - - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive& ar, const unsigned int /*version*/) - { - ar& fuAddress; - ar& fdTime; - ar& fdTot; - } + double fdTime; ///< Absolute time [ns] + double fdTot; ///< Tot [ns?] + uint32_t fuAddress; ///< Unique channel address + + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive& ar, const unsigned int /*version*/) + { + ar& fuAddress; + ar& fdTime; + ar& fdTot; + } #ifndef NO_ROOT ClassDefNV(CbmTofDigi, 3); diff --git a/external/InstallParameter.cmake b/external/InstallParameter.cmake index 74e6f43126..b368899baf 100644 --- a/external/InstallParameter.cmake +++ b/external/InstallParameter.cmake @@ -1,5 +1,7 @@ set(PARAMETER_VERSION ab9972f137bc52efd58bf3672e670a2d4fbcb1be) # 2025/02/11 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 3008aca25872492c1448c601b057b988dff41ac4 Mon Sep 17 00:00:00 2001 From: "s.zharko@gsi.de" <s.zharko@gsi.de> Date: Fri, 7 Feb 2025 18:45:23 +0100 Subject: [PATCH 2/4] BMON calibrator: flexible configuration --- algo/detectors/bmon/Calibrate.cxx | 118 ++++++++++++++------------- algo/detectors/bmon/Calibrate.h | 13 ++- algo/detectors/bmon/CalibrateSetup.h | 39 +++++---- algo/global/Reco.cxx | 8 +- core/data/tof/CbmTofAddress.h | 1 + external/InstallParameter.cmake | 2 - 6 files changed, 93 insertions(+), 88 deletions(-) diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx index 70d99daeb4..53b40e6b13 100644 --- a/algo/detectors/bmon/Calibrate.cxx +++ b/algo/detectors/bmon/Calibrate.cxx @@ -13,6 +13,7 @@ #include "CbmTofAddress.h" #include "util/TimingsFormat.h" +#include <bitset> #include <chrono> using cbm::algo::bmon::Calibrate; @@ -21,36 +22,56 @@ using fles::Subsystem; // --------------------------------------------------------------------------------------------------------------------- // -Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSmOffset(1, 0), fRpcOffset(1, 0) +Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSelectionBitsOffset(0) { // Initialize offset arrays for channel deadtime check - int32_t NbSm = fSetup.NbSm; - int32_t NbRpc = fSetup.NbRpc; - for (int32_t Sm = 0; Sm < NbSm; Sm++) { - fSmOffset.push_back(fSmOffset.back() + NbRpc); - for (int32_t Rpc = 0; Rpc < NbRpc; Rpc++) { - int32_t NbChan = fSetup.rpcs[Sm * NbRpc + Rpc].chanPar.size(); - fRpcOffset.push_back(fRpcOffset.back() + 2 * NbChan); //Factor 2 for channel sides + int nDiamondsInSetup = fSetup.diamonds.size(); + if (nDiamondsInSetup == 0) { + throw std::runtime_error("No diamonds found in the BMON calibration config"); + } + if (!(fSetup.selectionMask) != (nDiamondsInSetup == 1)) { + throw std::runtime_error("Wrong diamond selection mask: for a single diamond it must be zero, and for multiple" + " diamonds it must be non-zero"); + } + if (nDiamondsInSetup > 1) { + // Define the selection bit offset + while (!((fSetup.selectionMask >> fSelectionBitsOffset) % 2)) { + ++fSelectionBitsOffset; } + + // Sort the diamonds in the setup by their SM or Side or other distinguishing index + std::sort(fSetup.diamonds.begin(), fSetup.diamonds.end(), [&](const auto& lhs, const auto& rhs) { + return GetDiamondIndex(lhs.refAddress) < GetDiamondIndex(rhs.refAddress); + }); } - // **** DEBUG: BEGIN - for (size_t iO = 0; iO < fSmOffset.size(); ++iO) { - L_(info) << "SM offset: " << iO << ", " << fSmOffset[iO]; + // Remove the channel information from the reference address + for (auto& diamond : fSetup.diamonds) { + diamond.refAddress = CbmTofAddress::GetModFullId(diamond.refAddress); } - for (size_t iO = 0; iO < fRpcOffset.size(); ++iO) { - L_(info) << "RPC offset: " << iO << ", " << fRpcOffset[iO]; + + // Calculate the channel offsets, needed for the dead time + fChannelOffset = std::vector<size_t>(nDiamondsInSetup + 1, 0); // the last element -- total number of channels + for (int32_t iD = 0; iD < nDiamondsInSetup; ++iD) { + fChannelOffset[iD + 1] = fChannelOffset[iD] + fSetup.diamonds[iD].chanPar.size(); } - // **** DEBUG: END + fChannelDeadTime = std::vector<double>(fChannelOffset.back(), std::numeric_limits<double>::quiet_NaN()); - fChannelDeadTime = std::vector<double>(fRpcOffset.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 } // --------------------------------------------------------------------------------------------------------------------- // Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn) { - xpu::push_timer("TofCalibrate"); + xpu::push_timer("BmonCalibrate"); xpu::t_add_bytes(digiIn.size_bytes()); // --- Output data @@ -60,79 +81,60 @@ Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn) auto& monitor = result.second; calDigiOut.reserve(digiIn.size()); + // Reset the channel dead time std::fill(fChannelDeadTime.begin(), fChannelDeadTime.end(), std::numeric_limits<double>::quiet_NaN()); + const auto& diamonds = fSetup.diamonds; for (const auto& digi : digiIn) { uint32_t address = static_cast<uint32_t>(digi.GetAddress()); - const int32_t SmType{CbmTofAddress::GetSmType(address)}; - const int32_t Sm{CbmTofAddress::GetSmId(address)}; - const int32_t Rpc{CbmTofAddress::GetRpcId(address)}; - const int NbRpc{fSetup.NbRpc}; - - int32_t Chan{CbmTofAddress::GetChannelId(address)}; - int32_t Side{CbmTofAddress::GetChannelSide(address)}; - - // NOTE: Follow the hack in the CbmTofEventClusterizer::Exec() for the BMON digis and change the address - // of calibrated digis. In general it is incorrect and needs further investigation. - uint32_t calAddress = address; - if (Side == 1) { - Side = 0; - Chan += 6; - calAddress = CbmTofAddress::GetUniqueAddress(Sm, Rpc, Chan, Side, SmType); - } - - auto& rpcs = fSetup.rpcs; - if (SmType != fSetup.SmType || Sm * NbRpc + Rpc >= static_cast<int>(rpcs.size())) { + int32_t iChannel = CbmTofAddress::GetChannelId(address); + size_t iDiamond = GetDiamondIndex(address); + if (iDiamond >= diamonds.size() + || static_cast<uint32_t>(CbmTofAddress::GetModFullId(address)) != diamonds[iDiamond].refAddress) { monitor.fDigiCalibUnknownRPC++; - L_(error) << "Unknown BMON digi address: " << CbmTofAddress::ToString(calAddress); + L_(error) << "Unknown BMON digi address: " << CbmTofAddress::ToString(address) << ", iDiamond = " << iDiamond; continue; } - CalibrateSetup::Rpc& rpcPar = fSetup.rpcs[Sm * NbRpc + Rpc]; - CalibrateSetup::Channel& chanPar = rpcPar.chanPar[Chan]; + const auto& diamondPar = diamonds[iDiamond]; + const auto& channelPar = diamondPar.chanPar[iChannel]; // Check dead time - // FIXME: BMON digis (must) have only one side -> Fix the offsets, when mcbm2024_05 will be fixed - const size_t chanIdx = fRpcOffset[fSmOffset[Sm] + Rpc] + Chan + Side * rpcPar.chanPar.size(); - const double deadTime = fChannelDeadTime[chanIdx]; + const size_t iChannelGlb = fChannelOffset[iDiamond] + iChannel; + const double deadTime = fChannelDeadTime[iChannelGlb]; if (!std::isnan(deadTime) && digi.GetTime() <= deadTime) { - fChannelDeadTime[chanIdx] = digi.GetTime() + rpcPar.channelDeadtime; + fChannelDeadTime[iChannelGlb] = digi.GetTime() + diamondPar.channelDeadtime; monitor.fDigiDeadTimeCount++; continue; } - fChannelDeadTime[chanIdx] = digi.GetTime() + rpcPar.channelDeadtime; + fChannelDeadTime[iChannelGlb] = digi.GetTime() + diamondPar.channelDeadtime; // Create calibrated digi CbmBmonDigi& pCalDigi = calDigiOut.emplace_back(digi); - pCalDigi.SetAddress(calAddress); - - // Check if channel sides need to be swapped - if (rpcPar.swapChannelSides) { - pCalDigi.SetAddress(CbmTofAddress::GetUniqueAddress(Sm, Rpc, Chan, (0 == Side) ? 1 : 0, SmType)); - } + pCalDigi.SetAddress(address); // calibrate Digi Time - pCalDigi.SetTime(pCalDigi.GetTime() - chanPar.vCPTOff[Side]); + pCalDigi.SetTime(pCalDigi.GetTime() - channelPar.vCPTOff); // subtract Offset - const double dTot = std::max(pCalDigi.GetCharge() - chanPar.vCPTotOff[Side], 0.001); + const double dTot = std::max(pCalDigi.GetCharge() - channelPar.vCPTotOff, 0.001); // calibrate Digi charge (corresponds to TOF ToT) - pCalDigi.SetCharge(dTot * chanPar.vCPTotGain[Side]); + pCalDigi.SetCharge(dTot * channelPar.vCPTotGain); // walk correction - std::vector<double>& walk = chanPar.vCPWalk[Side]; - const double dTotBinSize = (rpcPar.TOTMax - rpcPar.TOTMin) / rpcPar.numClWalkBinX; - int32_t iWx = std::max((int32_t)((pCalDigi.GetCharge() - rpcPar.TOTMin) / dTotBinSize), 0); - iWx = std::min(iWx, rpcPar.numClWalkBinX - 1); + const std::vector<double>& walk = channelPar.vCPWalk; + const double dTotBinSize = (diamondPar.TOTMax - diamondPar.TOTMin) / diamondPar.numClWalkBinX; + int32_t iWx = std::max((int32_t)((pCalDigi.GetCharge() - diamondPar.TOTMin) / dTotBinSize), 0); + iWx = std::min(iWx, diamondPar.numClWalkBinX - 1); - const double dDTot = (pCalDigi.GetCharge() - rpcPar.TOTMin) / dTotBinSize - (double) iWx - 0.5; + const double dDTot = (pCalDigi.GetCharge() - diamondPar.TOTMin) / dTotBinSize - (double) iWx - 0.5; double dWT = walk[iWx]; // linear interpolation to next bin if (dDTot > 0) { - if (iWx < rpcPar.numClWalkBinX - 1) { + if (iWx < diamondPar.numClWalkBinX - 1) { dWT += dDTot * (walk[iWx + 1] - walk[iWx]); } } diff --git a/algo/detectors/bmon/Calibrate.h b/algo/detectors/bmon/Calibrate.h index 291a6882b4..72d3914794 100644 --- a/algo/detectors/bmon/Calibrate.h +++ b/algo/detectors/bmon/Calibrate.h @@ -13,7 +13,6 @@ #include "PartitionedVector.h" #include "bmon/CalibrateSetup.h" #include "tof/Calibrate.h" // for the monitor data -#include "tof/Clusterizer.h" #include <gsl/span> #include <optional> @@ -42,9 +41,15 @@ namespace cbm::algo::bmon resultType operator()(gsl::span<const CbmBmonDigi> digiIn); private: + /// \brief A digi address + size_t GetDiamondIndex(uint32_t address) const + { + return ((fSetup.selectionMask & address) >> fSelectionBitsOffset); + } + CalibrateSetup fSetup; ///< Parameters of calibrator - std::vector<double> fChannelDeadTime; ///< Storage for dead time check - std::vector<size_t> fSmOffset; ///< Super module offset - std::vector<size_t> fRpcOffset; ///< RPC offset + std::vector<size_t> fChannelOffset; ///< Channel offset: offset for the channel index of each diamond + std::vector<double> fChannelDeadTime; ///< Channel dead time + uint32_t fSelectionBitsOffset; ///< Number of bits to ther right from the first bit in the selection mask }; } // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/CalibrateSetup.h b/algo/detectors/bmon/CalibrateSetup.h index 0d9dedd93c..26803bd010 100644 --- a/algo/detectors/bmon/CalibrateSetup.h +++ b/algo/detectors/bmon/CalibrateSetup.h @@ -22,12 +22,12 @@ namespace cbm::algo::bmon /// \struct CalibrateSetup /// \brief BMON calibration per channel struct CalibrateSetup { - + // FIXME: remove 'v' from non-vector variable names struct Channel { - std::vector<double> vCPTOff; - std::vector<double> vCPTotGain; - std::vector<double> vCPTotOff; - std::vector<std::vector<double>> vCPWalk; + double vCPTOff; + double vCPTotGain; + double vCPTotOff; + std::vector<double> vCPWalk; CBM_YAML_PROPERTIES(yaml::Property(&Channel::vCPTOff, "vCPTOff", "CPT offset"), yaml::Property(&Channel::vCPTotGain, "vCPTotGain", "CP time over threshold gain"), @@ -35,7 +35,8 @@ namespace cbm::algo::bmon yaml::Property(&Channel::vCPWalk, "vCPWalk", "CP walk correction", YAML::Block, YAML::Flow)); }; - struct Rpc { + struct Diamond { + uint32_t refAddress; int32_t numClWalkBinX; double TOTMax; double TOTMin; @@ -43,25 +44,23 @@ namespace cbm::algo::bmon double channelDeadtime; std::vector<Channel> chanPar; - CBM_YAML_PROPERTIES(yaml::Property(&Rpc::numClWalkBinX, "numClWalkBinX", "number of walk correction bins"), - yaml::Property(&Rpc::TOTMax, "TOTMax", "maximum time over threshold"), - yaml::Property(&Rpc::TOTMin, "TOTMin", "minimum time over threshold"), - yaml::Property(&Rpc::swapChannelSides, "swapChannelSides", "flag for swapping channel sides"), - yaml::Property(&Rpc::channelDeadtime, "channelDeadtime", "channel dead time"), - yaml::Property(&Rpc::chanPar, "chanPar", "channel parameters")); + CBM_YAML_PROPERTIES( + yaml::Property(&Diamond::refAddress, "refAddress", "reference HW address to distinguish this BMON"), + yaml::Property(&Diamond::numClWalkBinX, "numClWalkBinX", "number of walk correction bins"), + yaml::Property(&Diamond::TOTMax, "TOTMax", "maximum time over threshold"), + yaml::Property(&Diamond::TOTMin, "TOTMin", "minimum time over threshold"), + yaml::Property(&Diamond::swapChannelSides, "swapChannelSides", "flag for swapping channel sides"), + yaml::Property(&Diamond::channelDeadtime, "channelDeadtime", "channel dead time"), + yaml::Property(&Diamond::chanPar, "chanPar", "channel parameters")); }; /* Members */ - int32_t SmType; - int32_t NbSm; - int32_t NbRpc; - std::vector<Rpc> rpcs; + uint32_t selectionMask; + std::vector<Diamond> diamonds; CBM_YAML_PROPERTIES( - yaml::Property(&CalibrateSetup::SmType, "SmType", "Super module type"), - yaml::Property(&CalibrateSetup::NbSm, "NbSm", "Number of SMs"), - yaml::Property(&CalibrateSetup::NbRpc, "NbRpc", "Number of RPCs"), - yaml::Property(&CalibrateSetup::rpcs, "rpcs", "Parameters of RPCs")); + yaml::Property(&CalibrateSetup::selectionMask, "selectionMask", "A mask to distinguish between different diamonds"), + yaml::Property(&CalibrateSetup::diamonds, "diamonds", "Parameters of each diamond")); }; } // namespace cbm::algo::bmon diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx index 9bdc35bb6b..b16a463d07 100644 --- a/algo/global/Reco.cxx +++ b/algo/global/Reco.cxx @@ -390,7 +390,7 @@ RecoResults Reco::Run(const fles::Timeslice& ts) std::set<uint32_t> bmonFoundAddressesUnp; // found addresses among unpacked bmon digis std::set<uint32_t> bmonFoundAddressesCal; // found addresses among clalibrated bmon digis - int nEvents = 20; //events.size(); + int nEvents = events.size(); for (int iE = 0; iE < nEvents; ++iE) { //L_(info) << "-------------- event #" << iE; const auto& event = events[iE]; @@ -428,9 +428,9 @@ RecoResults Reco::Run(const fles::Timeslice& ts) stsMinT = std::min(stsMinT, digi.GetTime()); stsMaxT = std::max(stsMaxT, digi.GetTime()); } - L_(info) << "BMON time: " << bmonMinT << ", " << bmonMaxT << ", " << (bmonMaxT - bmonMinT); - L_(info) << "TOF time: " << tofMinT << ", " << tofMaxT << ", " << (tofMaxT - tofMinT); - L_(info) << "STS time: " << stsMinT << ", " << stsMaxT << ", " << (stsMaxT - stsMinT); + //L_(info) << "BMON time: " << bmonMinT << ", " << bmonMaxT << ", " << (bmonMaxT - bmonMinT); + //L_(info) << "TOF time: " << tofMinT << ", " << tofMaxT << ", " << (tofMaxT - tofMinT); + //L_(info) << "STS time: " << stsMinT << ", " << stsMaxT << ", " << (stsMaxT - stsMinT); } L_(info) << "!!!! Found addresses in BMON after unpacking: "; for (auto address : bmonFoundAddressesUnp) { diff --git a/core/data/tof/CbmTofAddress.h b/core/data/tof/CbmTofAddress.h index 3abf9b1703..d174064bf2 100644 --- a/core/data/tof/CbmTofAddress.h +++ b/core/data/tof/CbmTofAddress.h @@ -182,6 +182,7 @@ public: static std::string ToString(int32_t address); private: + // FIXME: SZh 7.2.2025: Make these fields constexpr /** ** To adapt the address sub-fields repartition in size, ** you just need to change number of bits of the two sub-fields changing length. diff --git a/external/InstallParameter.cmake b/external/InstallParameter.cmake index b368899baf..74e6f43126 100644 --- a/external/InstallParameter.cmake +++ b/external/InstallParameter.cmake @@ -1,7 +1,5 @@ set(PARAMETER_VERSION ab9972f137bc52efd58bf3672e670a2d4fbcb1be) # 2025/02/11 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 4d3d93c35591bed35f8427be13d8d00442ed3c78 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 3/4] online: BMON hitfinder --- algo/CMakeLists.txt | 8 +- algo/detectors/bmon/Calibrate.cxx | 4 +- algo/detectors/bmon/Calibrate.h | 5 +- algo/detectors/bmon/CalibrateSetup.h | 4 +- algo/detectors/bmon/Clusterizer.cxx | 86 ++++++++++++++++++++ algo/detectors/bmon/Clusterizer.h | 57 +++++++++++++ algo/detectors/bmon/ClusterizerPars.h | 25 ++++++ algo/detectors/bmon/Hit.h | 103 +++++++++++++++++++++++ algo/detectors/bmon/Hitfind.cxx | 112 ++++++++++++++++++++++++++ algo/detectors/bmon/Hitfind.h | 60 ++++++++++++++ algo/detectors/bmon/HitfindSetup.h | 42 ++++++++++ algo/global/Reco.cxx | 67 ++++----------- algo/global/Reco.h | 2 + core/data/bmon/CbmBmonDigi.h | 10 ++- core/data/tof/CbmTofAddress.cxx | 12 ++- core/data/tof/CbmTofAddress.h | 76 ++++++++++++++++- 16 files changed, 606 insertions(+), 67 deletions(-) create mode 100644 algo/detectors/bmon/Clusterizer.cxx create mode 100644 algo/detectors/bmon/Clusterizer.h create mode 100644 algo/detectors/bmon/ClusterizerPars.h create mode 100644 algo/detectors/bmon/Hit.h create mode 100644 algo/detectors/bmon/Hitfind.cxx create mode 100644 algo/detectors/bmon/Hitfind.h create mode 100644 algo/detectors/bmon/HitfindSetup.h diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt index 58ac4397e4..bc9883747a 100644 --- a/algo/CMakeLists.txt +++ b/algo/CMakeLists.txt @@ -96,6 +96,11 @@ set(SRCS evselector/DigiEventSelectorConfig.cxx unpack/CommonUnpacker.cxx detectors/bmon/Calibrate.cxx + detectors/bmon/Clusterizer.cxx + detectors/bmon/Hitfind.cxx + detectors/bmon/ReadoutConfig.cxx + detectors/bmon/Unpack.cxx + detectors/bmon/UnpackMS.cxx detectors/sts/ChannelMaskSet.cxx detectors/sts/ReadoutConfig.cxx detectors/sts/HitfinderChain.cxx @@ -114,9 +119,6 @@ set(SRCS detectors/tof/UnpackMS.cxx detectors/tof/Hitfind.cxx detectors/tof/TrackingInterface.cxx - detectors/bmon/ReadoutConfig.cxx - detectors/bmon/Unpack.cxx - detectors/bmon/UnpackMS.cxx detectors/trd/Cluster.cxx detectors/trd/Clusterizer.cxx detectors/trd/Cluster2D.cxx diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx index 53b40e6b13..1a2a6af79d 100644 --- a/algo/detectors/bmon/Calibrate.cxx +++ b/algo/detectors/bmon/Calibrate.cxx @@ -47,7 +47,7 @@ Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSelectionBitsOffset // Remove the channel information from the reference address for (auto& diamond : fSetup.diamonds) { - diamond.refAddress = CbmTofAddress::GetModFullId(diamond.refAddress); + diamond.refAddress &= ~CbmTofAddress::GetChannelIdBitmask(); } // Calculate the channel offsets, needed for the dead time @@ -90,7 +90,7 @@ Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn) int32_t iChannel = CbmTofAddress::GetChannelId(address); size_t iDiamond = GetDiamondIndex(address); if (iDiamond >= diamonds.size() - || static_cast<uint32_t>(CbmTofAddress::GetModFullId(address)) != diamonds[iDiamond].refAddress) { + || (address & ~CbmTofAddress::GetChannelIdBitmask()) != diamonds[iDiamond].refAddress) { monitor.fDigiCalibUnknownRPC++; L_(error) << "Unknown BMON digi address: " << CbmTofAddress::ToString(address) << ", iDiamond = " << iDiamond; continue; diff --git a/algo/detectors/bmon/Calibrate.h b/algo/detectors/bmon/Calibrate.h index 72d3914794..0e602e6b4c 100644 --- a/algo/detectors/bmon/Calibrate.h +++ b/algo/detectors/bmon/Calibrate.h @@ -41,7 +41,8 @@ namespace cbm::algo::bmon resultType operator()(gsl::span<const CbmBmonDigi> digiIn); private: - /// \brief A digi address + /// \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 ((fSetup.selectionMask & address) >> fSelectionBitsOffset); @@ -49,7 +50,7 @@ namespace cbm::algo::bmon CalibrateSetup fSetup; ///< Parameters of calibrator std::vector<size_t> fChannelOffset; ///< Channel offset: offset for the channel index of each diamond - std::vector<double> fChannelDeadTime; ///< Channel dead time + std::vector<double> fChannelDeadTime; ///< Dead time, stored for a channel uint32_t fSelectionBitsOffset; ///< Number of bits to ther right from the first bit in the selection mask }; } // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/CalibrateSetup.h b/algo/detectors/bmon/CalibrateSetup.h index 26803bd010..eeeb58e178 100644 --- a/algo/detectors/bmon/CalibrateSetup.h +++ b/algo/detectors/bmon/CalibrateSetup.h @@ -40,7 +40,6 @@ namespace cbm::algo::bmon int32_t numClWalkBinX; double TOTMax; double TOTMin; - bool swapChannelSides; double channelDeadtime; std::vector<Channel> chanPar; @@ -49,7 +48,6 @@ namespace cbm::algo::bmon yaml::Property(&Diamond::numClWalkBinX, "numClWalkBinX", "number of walk correction bins"), yaml::Property(&Diamond::TOTMax, "TOTMax", "maximum time over threshold"), yaml::Property(&Diamond::TOTMin, "TOTMin", "minimum time over threshold"), - yaml::Property(&Diamond::swapChannelSides, "swapChannelSides", "flag for swapping channel sides"), yaml::Property(&Diamond::channelDeadtime, "channelDeadtime", "channel dead time"), yaml::Property(&Diamond::chanPar, "chanPar", "channel parameters")); }; @@ -59,7 +57,7 @@ namespace cbm::algo::bmon std::vector<Diamond> diamonds; CBM_YAML_PROPERTIES( - yaml::Property(&CalibrateSetup::selectionMask, "selectionMask", "A mask to distinguish between different diamonds"), + yaml::Property(&CalibrateSetup::selectionMask, "selectionMask", "A bit mask to distinguish between different diamonds"), yaml::Property(&CalibrateSetup::diamonds, "diamonds", "Parameters of each diamond")); }; diff --git a/algo/detectors/bmon/Clusterizer.cxx b/algo/detectors/bmon/Clusterizer.cxx new file mode 100644 index 0000000000..2d48416e35 --- /dev/null +++ b/algo/detectors/bmon/Clusterizer.cxx @@ -0,0 +1,86 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Clusterizer.cxx +/// \brief A clusterizer algorithm for BMON (implementation) +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + + +#include "bmon/Clusterizer.h" + +#include "AlgoFairloggerCompat.h" +#include "CbmBmonDigi.h" +#include "CbmTofAddress.h" + +#include <algorithm> +#include <iomanip> +#include <iostream> +#include <sstream> + +using cbm::algo::bmon::Clusterizer; + +// --------------------------------------------------------------------------------------------------------------------- +// +Clusterizer::Output_t Clusterizer::operator()(const Clusterizer::Input_t& digis) +{ + // Description: + // The input array of digis is traced through until the last element. If the current digi and the next digi are + // close in time and have neighboring channels, a single hit is produced. Otherwise a hit is produced from a + // single digi. The complexity of the algorithm is O(nDigis). Requirements: digis must be sorted in time + Output_t res; + if (digis.empty()) { + return res; + } + + auto& hits = std::get<0>(res); + auto& digiIndices = std::get<1>(res); + hits.reserve(digis.size()); + digiIndices.reserve(digis.size()); + auto itLast = std::prev(digis.end()); // iterator pointing to the last digi + bool bUsedWithPrevious{false}; // A flag: if the current digi was used together with the previous one + for (auto it = digis.begin(), in = it; it != itLast; ++it) { + if (bUsedWithPrevious) { + // skip a digi, if it was already used + bUsedWithPrevious = false; + continue; + } + in = std::next(it); + const auto& digiT = it->first; + const auto& digiN = in->first; + if (digiN.GetTime() - digiT.GetTime() < fParams.fdMaxTimeDist + && abs(digiN.GetChannel() - digiT.GetChannel()) == 1) { + // A hit consisting from two digis is found + hits.emplace_back(fParams.fAddress, digiT, digiN); + digiIndices.emplace_back(it->second); + bUsedWithPrevious = true; + } + else { + // A hit consisting from a single digi + hits.emplace_back(fParams.fAddress, digiT); + digiIndices.emplace_back(it->second); + } + } + if (!bUsedWithPrevious) { + // Create a hit from the last digi + hits.emplace_back(fParams.fAddress, itLast->first); + digiIndices.emplace_back(itLast->second); + } + return res; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Clusterizer::SelectDigi(const CbmBmonDigi& digi) const +{ + // Dead channel cut + if (fParams.fDeadStrips & (1 << CbmTofAddress::GetChannelId(digi.GetAddress()))) { + return false; + } + + // ??? Other cuts ??? Charge threshold? Also dead strips can be accounted already on the calibrator level, together + // with the cuts + + return true; +} diff --git a/algo/detectors/bmon/Clusterizer.h b/algo/detectors/bmon/Clusterizer.h new file mode 100644 index 0000000000..86b3c76e3b --- /dev/null +++ b/algo/detectors/bmon/Clusterizer.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Clusterizer.h +/// \brief A clusterizer algorithm for BMON +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "base/PODVector.h" +#include "bmon/ClusterizerPars.h" +#include "bmon/Hit.h" + +#include <cmath> +#include <cstdint> +#include <memory> +#include <utility> +#include <vector> + +class CbmBmonDigi; + +namespace cbm::algo::bmon +{ + /// \class Clusterizer + /// \brief A clusterizer algorithm for a BMON + /// + /// The algorithm is executed on a single hardware module + class Clusterizer { + public: + using Input_t = std::vector<std::pair<CbmBmonDigi, int32_t>>; ///< Input type + using Output_t = std::pair<std::vector<Hit>, PODVector<int32_t>>; ///< Output type + + /// \brief Constructor + /// \param params RPC parameters + explicit Clusterizer(ClusterizerPars params) : fParams(params) {} + + /// \brief Hit building function + Output_t operator()(const Input_t& digisInput); + + /// \brief Applies selection on a digis + /// \return true Digi is selected + /// \return false Digi is cut out + bool SelectDigi(const CbmBmonDigi& digi) const; + + private: + /// \brief Creates a hit from a single digi + Hit CreateHit(const CbmBmonDigi& digi) const; + + /// \brief Creates a hit from two digis + Hit CreateHit(const CbmBmonDigi& digiL, const CbmBmonDigi& digiR) const; + + + ClusterizerPars fParams; ///< parameters container + }; +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/ClusterizerPars.h b/algo/detectors/bmon/ClusterizerPars.h new file mode 100644 index 0000000000..0e2e65ecf9 --- /dev/null +++ b/algo/detectors/bmon/ClusterizerPars.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file ClusterizerPars.h +/// \brief BMON clusterizer parameters +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include <cstdint> +#include <vector> + +namespace cbm::algo::bmon +{ + /// \struct ClusterizerPars + /// \brief Clusterizer parameters for Diamond + struct ClusterizerPars { + uint32_t fAddress; ///< Address of the diamond (the channel bit field is 0) + uint32_t fDeadStrips; ///< Dead strip bitmask + double fdMaxTimeDist; ///< Maximum time difference between two consecutive digis to form a single hit + double fTimeRes; ///< Time resolution + }; +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/Hit.h b/algo/detectors/bmon/Hit.h new file mode 100644 index 0000000000..e4e1aef243 --- /dev/null +++ b/algo/detectors/bmon/Hit.h @@ -0,0 +1,103 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Hitfind.h +/// \brief A BMON hit class +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "CbmBmonDigi.h" + +#include <boost/serialization/access.hpp> + +#include <cstdint> +#include <vector> + +namespace cbm::algo::bmon +{ + /// \class Hit + /// \brief A BMON hit + class Hit { + public: + /// \brief Constructor from a single digi + /// \param address Address of the diamond + /// \param digi A digi + Hit(uint32_t address, const CbmBmonDigi& digi) : fAddress(address), fNofChannels(1) + { + fTime = digi.GetTime(); + fTimeError = 0.; // FIXME: provide a rule to set an error + } + + /// \brief Constructor from two digis + /// \param address Address of the diamond + /// \param digiL First digi + /// \param digiR Second digi + Hit(uint32_t address, const CbmBmonDigi& digiL, const CbmBmonDigi& digiR) : fAddress(address), fNofChannels(2) + { + double weightSum = digiL.GetCharge() + digiR.GetCharge(); + fTime = (digiL.GetTime() * digiL.GetCharge() + digiR.GetTime() * digiR.GetCharge()) / weightSum; + fTimeError = 0.; // FIXME: provide a rule to set an error + } + + /// \brief Constructor + /// \param address Address of diamond (the channel is not stored) + /// \param time Time of the hit [ns] + /// \param timeError Time error of the hit [ns] + /// \param nChannels Number of channels used (either one or two) + Hit(uint32_t address, double time, double timeError, uint8_t nChannels) + : fTime(time) + , fTimeError(timeError) + , fAddress(address) + , fNofChannels(nChannels) + { + } + + /// \brief Gets hardware address + uint32_t GetAddress() const { return fAddress; } + + /// \brief Gets number of channels + uint8_t GetNofChannels() const { return fNofChannels; } + + /// \brief Gets time [ns] + double GetTime() const { return fTime; } + + /// \brief Gets time error [ns] + double GetTimeError() const { return fTimeError; } + + /// \brief Sets address + /// \param address Hardware address + void SetAddress(uint32_t address) { fAddress = address; } + + /// \brief Sets number of channels + /// \param nofChannels Number of channels (digis), used to create a hit + void SetNofChannels(uint8_t nofChannels) { fNofChannels = nofChannels; } + + /// \brief Sets time + /// \param time Hit time [ns] + void SetTime(double time) { fTime = time; } + + /// \brief Sets time error + /// \param timeError Hit time error [ns] + void SetTimeError(double timeError) { fTimeError = timeError; } + + private: + double fTime{0.}; ///< Time [ns] + double fTimeError{0.}; ///< Time error [ns] + uint32_t fAddress{0}; ///< Assigned hit address + uint8_t fNofChannels{0}; ///< Number of channels used + + /// \brief Boost serialization function + friend class boost::serialization::access; + template<class Archive> + void serialize(Archive& ar, unsigned int /*version*/) + { + ar& fTime; + ar& fTimeError; + ar& fAddress; + ar& fNofChannels; + } + }; +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/Hitfind.cxx b/algo/detectors/bmon/Hitfind.cxx new file mode 100644 index 0000000000..b1e6794c8e --- /dev/null +++ b/algo/detectors/bmon/Hitfind.cxx @@ -0,0 +1,112 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Hitfind.cxx +/// \brief A BMON hitfinder steering class (implementation) +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + + +#include "Hitfind.h" + +#include "AlgoFairloggerCompat.h" +#include "compat/OpenMP.h" +#include "util/TimingsFormat.h" + +#include <chrono> + +using cbm::algo::bmon::ClusterizerPars; +using cbm::algo::bmon::Hitfind; +using cbm::algo::bmon::HitfindSetup; +using fles::Subsystem; + +// --------------------------------------------------------------------------------------------------------------------- +// +Hitfind::Hitfind(HitfindSetup setup, uint32_t nThreads) : fNofThreads(nThreads) +{ + // Create one algorithm per diamond and per thread + size_t nDiamondsInSetup{setup.diamonds.size()}; + if (nDiamondsInSetup == 0) { + throw std::runtime_error("No diamonds found in the BMON calibration config"); + } + if (!(setup.selectionMask) != (nDiamondsInSetup == 1)) { + throw std::runtime_error("Wrong diamond selection mask: for a single diamond it must be zero, and for multiple" + " diamonds it must be non-zero"); + } + + if (nDiamondsInSetup > 1) { + // Define the selection bit offset + while (!((setup.selectionMask >> fSelectionBitsOffset) % 2)) { + ++fSelectionBitsOffset; + } + + // Sort the diamonds in the setup by their SM or Side or other distinguishing index + std::sort(setup.diamonds.begin(), setup.diamonds.end(), [&](const auto& lhs, const auto& rhs) { + return GetDiamondIndex(lhs.refAddress) < GetDiamondIndex(rhs.refAddress); + }); + } + + fSelectionBitmask = setup.selectionMask; + fDiamondAddress = PODVector<uint32_t>(nDiamondsInSetup, 0); + + // Store diamond address + for (size_t iDiamond = 0; iDiamond < nDiamondsInSetup; ++iDiamond) { + fDiamondAddress[iDiamond] = setup.diamonds[iDiamond].refAddress & ~CbmTofAddress::GetChannelIdBitmask(); + } + + // Create and configure clusterizer algorithms per thread and per diamond + fAlgo = std::vector<std::vector<Clusterizer>>(fNofThreads, std::vector<Clusterizer>()); + for (auto& algoPerThread : fAlgo) { + algoPerThread.reserve(nDiamondsInSetup); + for (size_t iDiamond = 0; iDiamond < nDiamondsInSetup; ++iDiamond) { + auto par = std::make_unique<ClusterizerPars>(); + const auto& diamondPar = setup.diamonds[iDiamond]; + par->fAddress = diamondPar.refAddress; + par->fDeadStrips = diamondPar.deadStrips; + par->fdMaxTimeDist = diamondPar.maxTimeDist; + par->fTimeRes = diamondPar.timeRes; + algoPerThread.emplace_back(std::move(*par)); + } + } + L_(info) << "--- Configured hitfinder algorithms for BMON."; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +Hitfind::Output_t Hitfind::operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t iThread) +{ + Output_t res = {}; + auto& resHits = std::get<0>(res); + auto& resMoni = std::get<1>(res); + auto& resDigiIds = std::get<2>(res); + + auto& algoPerThread = fAlgo[iThread]; + + // Distribute digis over diamonds, apply cuts on this level (maybe the Calibrator is a more proper place for it) + size_t nDiamonds = algoPerThread.size(); + auto vDigiStorage = std::vector<Clusterizer::Input_t>(nDiamonds, Clusterizer::Input_t(0)); + for (int32_t iDigi = 0; iDigi < static_cast<int32_t>(digisIn.size()); ++iDigi) { + const auto& digi = digisIn[iDigi]; + size_t iDiamond = GetDiamondIndex(digi.GetAddress()); + if (algoPerThread[iDiamond].SelectDigi(digi)) { + vDigiStorage[iDiamond].emplace_back(digi, iDigi); + } + } + + // NOTE: I see no sense in storing a full channel address for different BMON hits, + // so for each hit the diamond address will be assigned: address & ~CbmTofAddress::GetChannelIdBitmask() + PODVector<Hit> vHitsFlat; // storage for clusters + PODVector<size_t> vNhitsPerDiamond(fDiamondAddress.size()); // number of hits per diamond + + for (size_t iDiamond = 0; iDiamond < algoPerThread.size(); ++iDiamond) { + auto [hits, digiIds] = algoPerThread[iDiamond](vDigiStorage[iDiamond]); + vNhitsPerDiamond[iDiamond] = hits.size(); + vHitsFlat.insert(vHitsFlat.end(), std::make_move_iterator(hits.begin()), std::make_move_iterator(hits.end())); + resDigiIds.insert(resDigiIds.end(), std::make_move_iterator(digiIds.begin()), + std::make_move_iterator(digiIds.end())); + } + + resHits = PartitionedVector(std::move(vHitsFlat), vNhitsPerDiamond, fDiamondAddress); + return res; +} diff --git a/algo/detectors/bmon/Hitfind.h b/algo/detectors/bmon/Hitfind.h new file mode 100644 index 0000000000..68d7d3a9b5 --- /dev/null +++ b/algo/detectors/bmon/Hitfind.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file Hitfind.h +/// \brief BMON hitfinder steering class +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "CbmBmonDigi.h" +#include "PODVector.h" +#include "PartitionedVector.h" +#include "bmon/Clusterizer.h" +#include "bmon/HitfindSetup.h" +#include "tof/Hitfind.h" // for tof::HitfindMonitorData + +#include <gsl/span> +#include <optional> +#include <sstream> +#include <vector> + +#include <xpu/host.h> + +namespace cbm::algo::bmon +{ + /// \brief TOF hit-finder monitor, re-used for BMON + using HitfindMonitorData = tof::HitfindMonitorData; + + /// \class Hitfind + /// \brief Hit-finder steering class for BMON + class Hitfind { + public: + /// \brief Output format + using Output_t = std::tuple<PartitionedVector<Hit>, HitfindMonitorData, PODVector<int32_t>>; + + /// \brief Constructor + /// \param setup Setup configuration + /// \param nThreads Number of threads (for event-based mode) + explicit Hitfind(HitfindSetup setup, uint32_t nThreads = 1); + + /// \brief Algorithm execution operator + /// \param digiIn A portion of digis in TS/event + /// \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); } + + 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 + + std::vector<std::vector<Clusterizer>> fAlgo; ///< Clusterizer algorithms [thread][diamond] + PODVector<uint32_t> fDiamondAddress; ///< Diamond address + }; +} // namespace cbm::algo::bmon diff --git a/algo/detectors/bmon/HitfindSetup.h b/algo/detectors/bmon/HitfindSetup.h new file mode 100644 index 0000000000..c98cc163ff --- /dev/null +++ b/algo/detectors/bmon/HitfindSetup.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file HitfindSetup.h +/// \brief Parameters of the BMON hitfinder +/// \since 06.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + + +#include "Definitions.h" +#include "yaml/Property.h" + +#include <string> +#include <vector> + +namespace cbm::algo::bmon +{ + /// \struct HitfindSetup + /// \brief Parameters for the BMON hitfinder + struct HitfindSetup { + struct Diamond { + u32 refAddress; + u32 deadStrips; + double maxTimeDist; + double timeRes; + + CBM_YAML_PROPERTIES( + yaml::Property(&Diamond::refAddress, "refAddress", "reference address of the diamond"), + yaml::Property(&Diamond::deadStrips, "deadStrips", "bit mask for dead strips"), + yaml::Property(&Diamond::maxTimeDist, "maxTimeDist", "maximum time distance"), + yaml::Property(&Diamond::timeRes, "timeRes", "time resolution")); + }; + + uint32_t selectionMask; + std::vector<Diamond> diamonds; + + CBM_YAML_PROPERTIES( + yaml::Property(&HitfindSetup::selectionMask, "selectionMask", "A bit mask to distinguish between different diamonds"), + yaml::Property(&HitfindSetup::diamonds, "diamonds", "Parameters of diamonds")); + }; +} // namespace cbm::algo::bmon diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx index b16a463d07..5b2698cb3f 100644 --- a/algo/global/Reco.cxx +++ b/algo/global/Reco.cxx @@ -15,6 +15,7 @@ #include "StsDigiQa.h" #include "TrackingSetup.h" #include "bmon/Calibrate.h" +#include "bmon/Hitfind.h" #include "bmon/ReadoutConfig.h" #include "bmon/Unpack.h" #include "ca/TrackingChain.h" @@ -214,8 +215,8 @@ void Reco::Init(const Options& opts) 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); + auto hitfindSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder); + fBmonHitFinder = std::make_unique<bmon::Hitfind>(hitfindSetup); } // TOF Hitfinder @@ -386,60 +387,26 @@ RecoResults Reco::Run(const fles::Timeslice& ts) } // ***** DEBUG: BEGIN - if constexpr (1) { - std::set<uint32_t> bmonFoundAddressesUnp; // found addresses among unpacked bmon digis - std::set<uint32_t> bmonFoundAddressesCal; // found addresses among clalibrated bmon digis - + if constexpr (0) { int nEvents = events.size(); + size_t nBmonHitsOneChannel{0}; + size_t nBmonHitsTwoChannels{0}; for (int iE = 0; iE < nEvents; ++iE) { - //L_(info) << "-------------- event #" << iE; const auto& event = events[iE]; - for (const auto& digi : event.fBmon) { - bmonFoundAddressesUnp.insert(digi.GetAddress()); - } - // Calibrate TOF digis: - auto [tofDigis, tofCalMonitor] = (*fTofCalibrator)(event.fTof); - auto [bmonDigis, bmonCalMonitor] = (*fBmonCalibrator)(event.fBmon); - - for (const auto& digi : bmonDigis) { - bmonFoundAddressesCal.insert(digi.GetAddress()); - } - - double bmonMinT = 1e+20; - double bmonMaxT = -1e+20; - for (const auto& digi : bmonDigis) { - //L_(info) << "BMON: " << digi.GetTime(); - bmonMinT = std::min(bmonMinT, digi.GetTime()); - bmonMaxT = std::max(bmonMaxT, digi.GetTime()); - } - double tofMinT = 1e+20; - double tofMaxT = -1e+20; - for (const auto& digi : tofDigis) { - //L_(info) << "TOF: " << digi.GetTime(); - tofMinT = std::min(tofMinT, digi.GetTime()); - tofMaxT = std::max(tofMaxT, digi.GetTime()); - } - double stsMinT = 1e+20; - double stsMaxT = -1e+20; - const auto& stsDigis = event.fSts; - for (const auto& digi : stsDigis) { - //L_(info) << "STS: " << digi.GetTime(); - stsMinT = std::min(stsMinT, digi.GetTime()); - stsMaxT = std::max(stsMaxT, digi.GetTime()); + auto [bmonDigis, bmonCalMonitor] = (*fBmonCalibrator)(event.fBmon); + auto [bmonHits, hitmonitor, digiindices] = (*fBmonHitFinder)(bmonDigis); + for (const auto& hit : bmonHits.Data()) { + if (hit.GetNofChannels() == 1) { + ++nBmonHitsOneChannel; + } + else { + ++nBmonHitsTwoChannels; + } } - //L_(info) << "BMON time: " << bmonMinT << ", " << bmonMaxT << ", " << (bmonMaxT - bmonMinT); - //L_(info) << "TOF time: " << tofMinT << ", " << tofMaxT << ", " << (tofMaxT - tofMinT); - //L_(info) << "STS time: " << stsMinT << ", " << stsMaxT << ", " << (stsMaxT - stsMinT); - } - L_(info) << "!!!! Found addresses in BMON after unpacking: "; - for (auto address : bmonFoundAddressesUnp) { - L_(info) << " - " << CbmTofAddress::ToString(address); - } - L_(info) << "!!!! Found addresses in BMON after calibration: "; - for (auto address : bmonFoundAddressesCal) { - L_(info) << " - " << CbmTofAddress::ToString(address); } + L_(info) << "!!!! BMON hits with two channels: " << nBmonHitsTwoChannels << " / " + << (nBmonHitsTwoChannels + nBmonHitsOneChannel); } // ***** DEBUG: END diff --git a/algo/global/Reco.h b/algo/global/Reco.h index 982a95cfa4..19027c8368 100644 --- a/algo/global/Reco.h +++ b/algo/global/Reco.h @@ -28,6 +28,7 @@ namespace cbm::algo { class Unpack; class Calibrate; + class Hitfind; } namespace much @@ -148,6 +149,7 @@ namespace cbm::algo // BMON std::unique_ptr<bmon::Unpack> fBmonUnpack; std::unique_ptr<bmon::Calibrate> fBmonCalibrator; + std::unique_ptr<bmon::Hitfind> fBmonHitFinder; // MUCH std::unique_ptr<much::Unpack> fMuchUnpack; diff --git a/core/data/bmon/CbmBmonDigi.h b/core/data/bmon/CbmBmonDigi.h index 7f0584e2fc..fffed5ca61 100644 --- a/core/data/bmon/CbmBmonDigi.h +++ b/core/data/bmon/CbmBmonDigi.h @@ -1,12 +1,13 @@ -/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt +/* Copyright (C) 2022-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt SPDX-License-Identifier: GPL-3.0-only - Authors: Pierre-Alain Loizeau, Volker Friese [committer] */ + Authors: Pierre-Alain Loizeau, Volker Friese [committer], Sergei Zharko */ #ifndef CBMBMONDIGI_H #define CBMBMONDIGI_H 1 #include "CbmDefs.h" +#include "CbmTofAddress.h" #ifndef NO_ROOT #include <Rtypes.h> // for ClassDef @@ -85,6 +86,11 @@ public: **/ double GetTime() const { return fTime; } + /** + ** @brief Gets channel ID + ** @return Channel ID + **/ + int32_t GetChannel() const { return CbmTofAddress::GetChannelId(fAddress); } /** @brief Charge ** @return Charge diff --git a/core/data/tof/CbmTofAddress.cxx b/core/data/tof/CbmTofAddress.cxx index 068504c399..29c1573452 100644 --- a/core/data/tof/CbmTofAddress.cxx +++ b/core/data/tof/CbmTofAddress.cxx @@ -1,6 +1,6 @@ -/* Copyright (C) 2013-2020 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt +/* Copyright (C) 2013-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt SPDX-License-Identifier: GPL-3.0-only - Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann */ + Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann, Segei Zharko */ /** @file CbmTofAddress.cxx ** @author Pierre-Alain Loizeau <loizeau@physi.uni-heidelberg.de> @@ -44,6 +44,14 @@ const int32_t CbmTofAddress::fgkiStripFullIdMask = + (((1 << fgkSmTypeBits) - 1) << fgkSmTypeOffset) + (((1 << fgkRpcIdBits) - 1) << fgkRpcIdOffset) + (((1 << fgkChannelIdBits) - 1) << fgkChannelIdOffset); +const int32_t CbmTofAddress::fgkSystemIdBitmask = (1 << CbmAddress::fgkSystemBits) - 1; +const int32_t CbmTofAddress::fgkSmIdBitmask = ((1 << fgkSmIdBits) - 1) << fgkSmIdOffset; +const int32_t CbmTofAddress::fgkSmTypeBitmask = ((1 << fgkSmTypeBits) - 1) << fgkSmTypeOffset; +const int32_t CbmTofAddress::fgkRpcIdBitmask = ((1 << fgkRpcIdBits) - 1) << fgkRpcIdOffset; +const int32_t CbmTofAddress::fgkChannelSideBitmask = ((1 << fgkChannelSideBits) - 1) << fgkChannelSideOffset; +const int32_t CbmTofAddress::fgkChannelIdBitmask = ((1 << fgkChannelIdBits) - 1) << fgkChannelIdOffset; +const int32_t CbmTofAddress::fgkRpcTypeBitmask = ((1 << fgkRpcTypeBits) - 1) << fgkRpcTypeOffset; + std::string CbmTofAddress::ToString(int32_t address) { using std::setfill; diff --git a/core/data/tof/CbmTofAddress.h b/core/data/tof/CbmTofAddress.h index d174064bf2..dfa1a18e89 100644 --- a/core/data/tof/CbmTofAddress.h +++ b/core/data/tof/CbmTofAddress.h @@ -1,6 +1,6 @@ -/* Copyright (C) 2013-2020 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt +/* Copyright (C) 2013-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt SPDX-License-Identifier: GPL-3.0-only - Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann */ + Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann, Sergei Zharko */ /** @file CbmTofAddress.h ** @author Pierre-Alain Loizeau <loizeau@physi.uni-heidelberg.de> @@ -54,6 +54,37 @@ public: /** Destructor **/ virtual ~CbmTofAddress() {}; + /** Component bitmasks **/ + /** Gets a bitmask for the System bit field + ** @return a bitmask with the bits set only for the System bit field + **/ + static int32_t GetSystemIdBitmask() { return fgkSystemIdBitmask; } + /** Gets a bitmask for the Super Module ID bit field + ** @return a bitmask with the bits set only for the Super Module ID bit field + **/ + static int32_t GetSmIdBitmask() { return fgkSmIdBitmask; } + /** Gets a bitmask for the Super Module Type bit field + ** @return a bitmask with the bits set only for the Super Module Type bit field + **/ + static int32_t GetSmTypeBitmask() { return fgkSmTypeBitmask; } + /** Gets a bitmask for the RPC ID bit field + ** @return a bitmask with the bits set only for the RPC ID bit field + **/ + static int32_t GetRpcIdBitmask() { return fgkRpcIdBitmask; } + /** Gets a bitmask for the Channel Side bit field + ** @return a bitmask with the bits set only for the Channel Side bit field + **/ + static int32_t GetChannelSideBitmask() { return fgkChannelSideBitmask; } + /** Gets a bitmask for the Channel ID bit field + ** @return a bitmask with the bits set only for the Channel ID bit field + **/ + static int32_t GetChannelIdBitmask() { return fgkChannelIdBitmask; } + /** Gets a bitmask for the RPC Type bit field + ** @return a bitmask with the bits set only for the RPC Type bit field + **/ + static int32_t GetRpcTypeBitmask() { return fgkRpcTypeBitmask; } + + /** Field size accessors **/ /** Number of bits for Super Module Id in the address field ** @return Number of bits @@ -76,6 +107,33 @@ public: **/ static int32_t GetNofChSideBits() { return fgkChannelSideBits; }; + + /** Bit field offsets **/ + /** Gets and offset for the System bit field + ** @return and offset with the bits set only for the System bit field + **/ + static int32_t GetSmIdOffset() { return fgkSmIdOffset; } + /** Gets and offset for the Super Module Type bit field + ** @return and offset with the bits set only for the Super Module Type bit field + **/ + static int32_t GetSmTypeOffset() { return fgkSmTypeOffset; } + /** Gets and offset for the RPC ID bit field + ** @return and offset with the bits set only for the RPC ID bit field + **/ + static int32_t GetRpcIdOffset() { return fgkRpcIdOffset; } + /** Gets and offset for the Channel Side bit field + ** @return and offset with the bits set only for the Channel Side bit field + **/ + static int32_t GetChannelSideOffset() { return fgkChannelSideOffset; } + /** Gets and offset for the Channel ID bit field + ** @return and offset with the bits set only for the Channel ID bit field + **/ + static int32_t GetChannelIdOffset() { return fgkChannelIdOffset; } + /** Gets and offset for the RPC Type bit field + ** @return and offset with the bits set only for the RPC Type bit field + **/ + static int32_t GetRpcTypeOffset() { return fgkRpcTypeOffset; } + /** Maskers **/ /** Get the Super Module Id from the address ** @param address Unique address @@ -154,7 +212,9 @@ public: { return (GetModFullId(addressA) == GetModFullId(addressB)) ? true : false; }; - + /** + ** @brief Sets the channel ID to the address + **/ static uint32_t ConvertCbmTofDetectorInfo(CbmTofDetectorInfo infoInput) { // For now assume that the system ID will always be correct @@ -251,6 +311,16 @@ public: ** For the detector strip Id determination (ignore RPC type and side) **/ static const int32_t fgkiStripFullIdMask; + + + /** Individual bitmasks for each bit field **/ + static const int32_t fgkSystemIdBitmask; //< A bitmask for System ID + static const int32_t fgkSmIdBitmask; //< A bitmask for SM + static const int32_t fgkSmTypeBitmask; //< A bitmask for SM Type + static const int32_t fgkRpcIdBitmask; //< A bitmask for RPC ID + static const int32_t fgkChannelSideBitmask; //< A bitmask for Channel Side + static const int32_t fgkChannelIdBitmask; //< A bitmask for Channel ID + static const int32_t fgkRpcTypeBitmask; //< A bitmask for RPC Type }; #endif // CBMTOFADDRESS_H -- GitLab From be38224e64b5a74c803bfe45fc0be295efadd4a1 Mon Sep 17 00:00:00 2001 From: "s.zharko@gsi.de" <s.zharko@gsi.de> Date: Tue, 11 Feb 2025 05:07:31 +0100 Subject: [PATCH 4/4] online: BMON hitfinder QA --- algo/CMakeLists.txt | 3 + algo/ca/qa/CaQa.h | 2 +- algo/detectors/bmon/Calibrate.cxx | 4 +- algo/detectors/bmon/Calibrate.h | 2 +- algo/detectors/bmon/Clusterizer.cxx | 1 + algo/detectors/bmon/Hitfind.cxx | 1 + algo/detectors/bmon/HitfindSetup.h | 1 + algo/global/Reco.cxx | 27 ++--- algo/global/Reco.h | 2 + algo/qa/PadConfig.h | 19 +++- algo/qa/QaTaskHeader.h | 7 +- algo/qa/hitfind/BmonHitfindQa.cxx | 112 ++++++++++++++++++++ algo/qa/hitfind/BmonHitfindQa.h | 112 ++++++++++++++++++++ algo/qa/hitfind/BmonHitfindQaParameters.cxx | 58 ++++++++++ algo/qa/hitfind/BmonHitfindQaParameters.h | 50 +++++++++ 15 files changed, 378 insertions(+), 23 deletions(-) create mode 100644 algo/qa/hitfind/BmonHitfindQa.cxx create mode 100644 algo/qa/hitfind/BmonHitfindQa.h create mode 100644 algo/qa/hitfind/BmonHitfindQaParameters.cxx create mode 100644 algo/qa/hitfind/BmonHitfindQaParameters.h diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt index bc9883747a..a2a052fa40 100644 --- a/algo/CMakeLists.txt +++ b/algo/CMakeLists.txt @@ -154,6 +154,8 @@ set(SRCS qa/QaData.cxx qa/RecoGeneralQa.cxx qa/QaManager.cxx + qa/hitfind/BmonHitfindQa.cxx + qa/hitfind/BmonHitfindQaParameters.cxx qa/unpack/StsDigiQa.cxx ca/TrackingSetup.cxx ca/TrackingChain.cxx @@ -191,6 +193,7 @@ target_include_directories(Algo ${CMAKE_CURRENT_SOURCE_DIR}/detectors ${CMAKE_CURRENT_SOURCE_DIR}/qa ${CMAKE_CURRENT_SOURCE_DIR}/qa/unpack + ${CMAKE_CURRENT_SOURCE_DIR}/qa/hitfind ${CMAKE_CURRENT_SOURCE_DIR}/kf ${CMAKE_CURRENT_SOURCE_DIR}/kf/core ${CMAKE_CURRENT_SOURCE_DIR}/kf/core/utils diff --git a/algo/ca/qa/CaQa.h b/algo/ca/qa/CaQa.h index 655d6fa50b..1c42b60562 100644 --- a/algo/ca/qa/CaQa.h +++ b/algo/ca/qa/CaQa.h @@ -54,7 +54,7 @@ namespace cbm::algo::ca static constexpr HitSetArray_t<EHitSet> kHitSets = {EHitSet::Input, EHitSet::Used}; public: - /// \brief Default destructor + /// \brief Constructor /// \param pManager Pointer to the QA manager /// \param name Name of the QA (directory) Qa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {} diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx index 1a2a6af79d..d551985490 100644 --- a/algo/detectors/bmon/Calibrate.cxx +++ b/algo/detectors/bmon/Calibrate.cxx @@ -77,8 +77,8 @@ Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn) // --- Output data resultType result = {}; - auto& calDigiOut = result.first; - auto& monitor = result.second; + auto& calDigiOut = std::get<0>(result); + auto& monitor = std::get<1>(result); calDigiOut.reserve(digiIn.size()); // Reset the channel dead time diff --git a/algo/detectors/bmon/Calibrate.h b/algo/detectors/bmon/Calibrate.h index 0e602e6b4c..644fd20320 100644 --- a/algo/detectors/bmon/Calibrate.h +++ b/algo/detectors/bmon/Calibrate.h @@ -30,7 +30,7 @@ namespace cbm::algo::bmon /// \brief Algorithm to calibrate BMon digis class Calibrate { public: - using resultType = std::pair<std::vector<CbmBmonDigi>, CalibrateMonitorData>; + using resultType = std::tuple<std::vector<CbmBmonDigi>, CalibrateMonitorData>; /// \brief Constructor /// \param params Calibration parameters diff --git a/algo/detectors/bmon/Clusterizer.cxx b/algo/detectors/bmon/Clusterizer.cxx index 2d48416e35..d3a8e3d4ab 100644 --- a/algo/detectors/bmon/Clusterizer.cxx +++ b/algo/detectors/bmon/Clusterizer.cxx @@ -67,6 +67,7 @@ Clusterizer::Output_t Clusterizer::operator()(const Clusterizer::Input_t& digis) hits.emplace_back(fParams.fAddress, itLast->first); digiIndices.emplace_back(itLast->second); } + return res; } diff --git a/algo/detectors/bmon/Hitfind.cxx b/algo/detectors/bmon/Hitfind.cxx index b1e6794c8e..d39a46f6c4 100644 --- a/algo/detectors/bmon/Hitfind.cxx +++ b/algo/detectors/bmon/Hitfind.cxx @@ -86,6 +86,7 @@ Hitfind::Output_t Hitfind::operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t i // Distribute digis over diamonds, apply cuts on this level (maybe the Calibrator is a more proper place for it) size_t nDiamonds = algoPerThread.size(); auto vDigiStorage = std::vector<Clusterizer::Input_t>(nDiamonds, Clusterizer::Input_t(0)); + for (int32_t iDigi = 0; iDigi < static_cast<int32_t>(digisIn.size()); ++iDigi) { const auto& digi = digisIn[iDigi]; size_t iDiamond = GetDiamondIndex(digi.GetAddress()); diff --git a/algo/detectors/bmon/HitfindSetup.h b/algo/detectors/bmon/HitfindSetup.h index c98cc163ff..c958cbc17c 100644 --- a/algo/detectors/bmon/HitfindSetup.h +++ b/algo/detectors/bmon/HitfindSetup.h @@ -7,6 +7,7 @@ /// \since 06.02.2025 /// \author Sergei Zharko <s.zharko@gsi.de> +#pragma once #include "Definitions.h" #include "yaml/Property.h" diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx index 5b2698cb3f..8460ea28bf 100644 --- a/algo/global/Reco.cxx +++ b/algo/global/Reco.cxx @@ -24,6 +24,7 @@ #include "evbuild/Config.h" #include "much/Unpack.h" #include "qa/QaManager.h" +#include "qa/hitfind/BmonHitfindQa.h" #include "rich/Unpack.h" #include "sts/ChannelMaskSet.h" #include "sts/HitfinderChain.h" @@ -217,6 +218,12 @@ void Reco::Init(const Options& opts) 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 @@ -236,7 +243,7 @@ void Reco::Init(const Options& opts) // Tracking if (Opts().Has(Step::Tracking)) { - if (Opts().Has(QaStep::Tracking)) { + if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) { fTracking = std::make_unique<TrackingChain>(fQaManager, "CaTimeslice"); } else { @@ -389,24 +396,18 @@ 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: auto [bmonDigis, bmonCalMonitor] = (*fBmonCalibrator)(event.fBmon); - auto [bmonHits, hitmonitor, digiindices] = (*fBmonHitFinder)(bmonDigis); - for (const auto& hit : bmonHits.Data()) { - if (hit.GetNofChannels() == 1) { - ++nBmonHitsOneChannel; - } - else { - ++nBmonHitsTwoChannels; - } + auto [bmonHits, hitmonitor, digiIndices] = (*fBmonHitFinder)(bmonDigis); + if (fBmonHitFinderQa != nullptr) { + fBmonHitFinderQa->RegisterDigis(&bmonDigis); + fBmonHitFinderQa->RegisterHits(&bmonHits); + fBmonHitFinderQa->RegisterDigiIndices(&digiIndices); + fBmonHitFinderQa->Exec(); } } - L_(info) << "!!!! BMON hits with two channels: " << nBmonHitsTwoChannels << " / " - << (nBmonHitsTwoChannels + nBmonHitsOneChannel); } // ***** DEBUG: END diff --git a/algo/global/Reco.h b/algo/global/Reco.h index 19027c8368..6182decdad 100644 --- a/algo/global/Reco.h +++ b/algo/global/Reco.h @@ -29,6 +29,7 @@ namespace cbm::algo class Unpack; class Calibrate; class Hitfind; + class HitfindQa; } namespace much @@ -150,6 +151,7 @@ namespace cbm::algo std::unique_ptr<bmon::Unpack> fBmonUnpack; std::unique_ptr<bmon::Calibrate> fBmonCalibrator; std::unique_ptr<bmon::Hitfind> fBmonHitFinder; + std::unique_ptr<bmon::HitfindQa> fBmonHitFinderQa; // MUCH std::unique_ptr<much::Unpack> fMuchUnpack; diff --git a/algo/qa/PadConfig.h b/algo/qa/PadConfig.h index 4d6ac5376c..104b214426 100644 --- a/algo/qa/PadConfig.h +++ b/algo/qa/PadConfig.h @@ -43,6 +43,15 @@ namespace cbm::algo::qa { } + /// \brief Constructor from a single histogram + /// \tparam Hist Histogram class + /// \param hist Histogram object + /// \param opt Draw options for the histogram + template<class Hist> + PadConfig(const Hist* hist, std::string_view opt) + { + RegisterHistogram(hist, opt); + } /// \brief Copy constructor PadConfig(const PadConfig&) = default; @@ -104,11 +113,11 @@ namespace cbm::algo::qa std::string ToString() const; private: - bool fbGridX = false; ///< Grid flag for x-axis - bool fbGridY = false; ///< Grid flag for y-axis - bool fbLogX = false; ///< Log flag for x-axis - bool fbLogY = false; ///< Log flag for y-axis - bool fbLogZ = false; ///< Log flag for z-axis + bool fbGridX{false}; ///< Grid flag for x-axis + bool fbGridY{false}; ///< Grid flag for y-axis + bool fbLogX{false}; ///< Log flag for x-axis + bool fbLogY{false}; ///< Log flag for y-axis + bool fbLogZ{false}; ///< Log flag for z-axis std::vector<std::pair<std::string, std::string>> fvObjectList; ///< List of objects on the pad }; diff --git a/algo/qa/QaTaskHeader.h b/algo/qa/QaTaskHeader.h index ca41383152..ceb03e5ad4 100644 --- a/algo/qa/QaTaskHeader.h +++ b/algo/qa/QaTaskHeader.h @@ -24,7 +24,8 @@ namespace cbm::algo::qa /// \param pManager a QA-manager /// \param name A name of the task (histograms directory) TaskHeader(const std::unique_ptr<Manager>& pManager, std::string_view name) - : fpData(pManager != nullptr ? pManager->GetData() : nullptr) + : fsName(name) + , fpData(pManager != nullptr ? pManager->GetData() : nullptr) { if (fpData != nullptr) { fpData->RegisterNewTask(name); @@ -52,6 +53,9 @@ namespace cbm::algo::qa /// the fpData instance is not defined, and no actions on the task should be performed bool IsActive() const { return static_cast<bool>(fpData != nullptr); } + /// \brief Gets name of the task + const std::string& GetTaskName() { return fsName; } + protected: /// \brief Adds a canvas configuration /// \param canvas A CanvasConfig object @@ -68,6 +72,7 @@ namespace cbm::algo::qa } private: + std::string fsName{}; ///< Name of the task std::shared_ptr<Data> fpData{nullptr}; ///< An instance of the QA data (shared between different tasks) }; } // namespace cbm::algo::qa diff --git a/algo/qa/hitfind/BmonHitfindQa.cxx b/algo/qa/hitfind/BmonHitfindQa.cxx new file mode 100644 index 0000000000..0bf088900f --- /dev/null +++ b/algo/qa/hitfind/BmonHitfindQa.cxx @@ -0,0 +1,112 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file BmonHitfindQa.cxx +/// \brief A BMON hitfinder QA (implementation) +/// \since 09.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#include "qa/hitfind/BmonHitfindQa.h" + +#include "CbmBmonDigi.h" +#include "CbmTofAddress.h" +#include "bmon/Hit.h" +#include "qa/Histogram.h" + +#include <fmt/format.h> + +using cbm::algo::bmon::HitfindQa; + +// --------------------------------------------------------------------------------------------------------------------- +// +void HitfindQa::Init() +try { + using fmt::format; + if (!IsActive()) { + return; + } + + size_t nDiamonds = fParameters.diamonds.size(); + if (nDiamonds < 1) { + throw std::runtime_error("parameters were not initialized. Please, provide the configuration using the function " + "HitfindQa::InitParameters(calSetup, hitSetup)"); + } + + fvphDigiOccupVsChan.resize(nDiamonds, nullptr); + fvphDigiChargeVsChan.resize(nDiamonds, nullptr); + fvphHitNofChan.resize(nDiamonds, nullptr); + fvphHitTimeDiff.resize(nDiamonds, nullptr); + + for (size_t iD = 0; iD < nDiamonds; ++iD) { + const auto& diamondPar = fParameters.diamonds[iD]; + int nCh = diamondPar.nChannels; + auto sDN = format("_diamond_{:#08x}", diamondPar.address); // diamond suffix + + // Histograms initialisation + /* clang-format off */ + fvphDigiOccupVsChan[iD] = MakeObj<qa::H1D>( + format("bmon_digi_occup_channel{}", sDN), + format("BMON-{} digi occupancy vs. channel;channel;counts", iD), + nCh, -0.5, nCh - 0.5); + fvphDigiChargeVsChan[iD] = MakeObj<qa::H2D>( + format("bmon_digi_charge_channel{}", sDN), + format("BMON-{} digi charge vs. channel;channel;charge;counts", iD), + nCh, -0.5, nCh - 0.5, kChrgB, kChrgL, kChrgU); + fvphHitNofChan[iD] = MakeObj<qa::H1D>( + format("bmon_hit_nChannels{}", sDN), + format("BMON-{} hit number of channels;N_{{chan}};counts", iD), + 2, 0.5, 2.5); + fvphHitTimeDiff[iD] = MakeObj<qa::H1D>( + format("bmon_hit_time_diff{}", sDN), + format("BMON-{} digi time difference in a hit formed from two digis;#Delta t_{{digi}} [ns];counts", iD), + kDtimeB, kDtimeL, kDtimeU); + /* clang-format on */ + + // Canvas initialization + auto cName = format("{}/bmon{}", GetTaskName(), sDN); + auto cTitl = format("BMON-{}", iD); + auto canv = qa::CanvasConfig(cName, cTitl, 2, 2); + canv.AddPadConfig(qa::PadConfig(fvphDigiOccupVsChan[iD], "hist")); // (0,0) + canv.AddPadConfig(qa::PadConfig(fvphDigiChargeVsChan[iD], "colz")); // (1,0) + canv.AddPadConfig(qa::PadConfig(fvphHitNofChan[iD], "hist")); // (0,1) + canv.AddPadConfig(qa::PadConfig(fvphHitTimeDiff[iD], "hist")); // (1,1) + AddCanvasConfig(canv); + } +} +catch (const std::exception& err) { + L_(fatal) << "bmon::HitfindQa: initialization failed. Reason: " << err.what(); + throw std::runtime_error("bmon::HitfindQa initialization failure"); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void HitfindQa::Exec() +{ + if (!IsActive()) { + return; + } + + // Fill digi distributions + for (const auto& digi : *fpDigis) { + size_t iDiamond = fParameters.GetDiamondIndex(digi.GetAddress()); + int32_t chan = digi.GetChannel(); + fvphDigiOccupVsChan[iDiamond]->Fill(chan); + fvphDigiChargeVsChan[iDiamond]->Fill(chan, digi.GetCharge()); + } + + // Fill hit distributions + const auto& hits = fpHits->Data(); + for (size_t iH = 0; iH < hits.size(); ++iH) { + const auto& hit = hits[iH]; + size_t iDiamond = fParameters.GetDiamondIndex(hit.GetAddress()); + int nChannels = hit.GetNofChannels(); + fvphHitNofChan[iDiamond]->Fill(nChannels); + if (nChannels == 2) { + int32_t iDigi = (*fpDigiIndices)[iH]; + const auto& digiF = (*fpDigis)[iDigi]; + const auto& digiS = (*fpDigis)[iDigi + 1]; + fvphHitTimeDiff[iDiamond]->Fill(digiS.GetTime() - digiF.GetTime()); + } + } +} diff --git a/algo/qa/hitfind/BmonHitfindQa.h b/algo/qa/hitfind/BmonHitfindQa.h new file mode 100644 index 0000000000..020f62fa82 --- /dev/null +++ b/algo/qa/hitfind/BmonHitfindQa.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file BmonHitfindQa.h +/// \brief A BMON hitfinder QA +/// \since 07.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "PODVector.h" +#include "PartitionedVector.h" +#include "qa/QaTaskHeader.h" +#include "qa/hitfind/BmonHitfindQaParameters.h" + +class CbmBmonDigi; + +namespace cbm::algo +{ + namespace qa + { + class H1D; + class H2D; + } // namespace qa + + namespace bmon + { + class Hit; + } +} // namespace cbm::algo + +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 + /// \param pManager Pointer to the QA manager + /// \param name Name of the QA (directory) + 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 + HitfindQa(const HitfindQa&) = delete; + + /// \brief Move constructor + HitfindQa(HitfindQa&&) = delete; + + /// \brief Destructor + ~HitfindQa() = default; + + /// \brief Copy assignment operator + HitfindQa& operator=(const HitfindQa&) = delete; + + /// \brief Move assignment operator + HitfindQa& operator=(HitfindQa&&) = delete; + + /// \brief Executes the task, fills the histograms + void Exec(); + + /// \brief Initialized the task + void Init(); + + /// \brief Initialisation of the parameters + void InitParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup) + { + fParameters = std::move(HitfindQaParameters(calSetup, hitSetup)); + } + + /// \brief Registers a sample of digis + /// \param pDigis A pointer to a vector of digis + void RegisterDigis(const std::vector<CbmBmonDigi>* pDigis) { fpDigis = pDigis; } + + /// \brief Registers a sample of hits + /// \param pHits A pointer to a vector of hits + void RegisterHits(const PartitionedVector<bmon::Hit>* pHits) { fpHits = pHits; } + + /// \brief Registers a sample of digi indices, used by hits + /// \param pDigiIndices A pointer to a vector of digi indices + void RegisterDigiIndices(const PODVector<int32_t>* pDigiIndices) { fpDigiIndices = pDigiIndices; } + + private: + //* Constants + static constexpr int kChrgB = 150; ///< charge scale: number of bins + static constexpr double kChrgL = 0.; ///< charge scale: lower bound + static constexpr double kChrgU = 150.; ///< charge scale: upper bound + static constexpr int kDtimeB = 40; ///< digi time difference: number of bins + static constexpr double kDtimeL = 0.; ///< digi time difference: lower bound [ns] + static constexpr double kDtimeU = 2.0; ///< digi time difference: upper bound [ns] + + //* Parameters + HitfindQaParameters fParameters; ///< Parameters of the hit finder QA + + //* Data samples + const std::vector<CbmBmonDigi>* fpDigis{nullptr}; ///< Pointer to BMON digi sample + const PartitionedVector<bmon::Hit>* fpHits{nullptr}; ///< Pointer to BMON hit sample + const PODVector<int32_t>* fpDigiIndices{nullptr}; ///< Pointer to BMON digi indices, used by hits + + //* Histograms + std::vector<qa::H1D*> fvphDigiOccupVsChan; ///< Digi occupancy vs. channel [diamond] + std::vector<qa::H2D*> fvphDigiChargeVsChan; ///< Digi charge vs channel [diamond] + std::vector<qa::H1D*> fvphHitNofChan; ///< Hit number of channels [diamond] + std::vector<qa::H1D*> fvphHitTimeDiff; ///< Time difference of two digis in a hit [diamond] + }; +} // namespace cbm::algo::bmon diff --git a/algo/qa/hitfind/BmonHitfindQaParameters.cxx b/algo/qa/hitfind/BmonHitfindQaParameters.cxx new file mode 100644 index 0000000000..aa7f4aadce --- /dev/null +++ b/algo/qa/hitfind/BmonHitfindQaParameters.cxx @@ -0,0 +1,58 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file BmonHitfindQaParameters.cxx +/// \brief A BMON hitfinder QA parameter configuration (implementation) +/// \since 10.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#include "qa/hitfind/BmonHitfindQaParameters.h" + +#include "AlgoFairloggerCompat.h" +#include "CbmTofAddress.h" + +using cbm::algo::bmon::CalibrateSetup; +using cbm::algo::bmon::HitfindQaParameters; +using cbm::algo::bmon::HitfindSetup; + +// --------------------------------------------------------------------------------------------------------------------- +// +HitfindQaParameters::HitfindQaParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup) +{ + + this->selectionMask = calSetup.selectionMask; + if (this->selectionMask != hitSetup.selectionMask) { + throw std::runtime_error("Mismatch of the selection bitmask in the BMON CalibrateSetup and HitfindSetup configs"); + } + + auto nDiamonds = calSetup.diamonds.size(); + if (nDiamonds != hitSetup.diamonds.size()) { + throw std::runtime_error("Mismatch of number of diamonds in the BMON CalibrateSetup and HitfindSetup configs"); + } + + if (nDiamonds > 1) { + while (!((this->selectionMask >> fSelectionBitsOffset) % 2)) { + ++fSelectionBitsOffset; + } + } + + this->diamonds.resize(nDiamonds); + for (const auto& calDiamond : calSetup.diamonds) { + uint32_t address = calDiamond.refAddress & ~CbmTofAddress::GetChannelIdBitmask(); + auto& thisDiamond = this->diamonds[GetDiamondIndex(address)]; + thisDiamond.address = address; + thisDiamond.nChannels = calDiamond.chanPar.size(); + } + + for (const auto& hitDiamond : hitSetup.diamonds) { + int32_t address = hitDiamond.refAddress & ~CbmTofAddress::GetChannelIdBitmask(); + auto& thisDiamond = this->diamonds[GetDiamondIndex(address)]; + if (thisDiamond.address != address) { + throw std::runtime_error("Mismatch between diamond addresses in BMON CalibrateSetup and HitfindSetup configs"); + } + thisDiamond.deadStrips = hitDiamond.deadStrips; + thisDiamond.timeRes = hitDiamond.timeRes; + thisDiamond.maxTimeDist = hitDiamond.maxTimeDist; + } +} diff --git a/algo/qa/hitfind/BmonHitfindQaParameters.h b/algo/qa/hitfind/BmonHitfindQaParameters.h new file mode 100644 index 0000000000..25f524ba87 --- /dev/null +++ b/algo/qa/hitfind/BmonHitfindQaParameters.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt + SPDX-License-Identifier: GPL-3.0-only + Authors: Sergei Zharko [committer] */ + +/// \file BmonHitfindQaParameters.h +/// \brief A BMON hitfinder QA parameter configuration +/// \since 10.02.2025 +/// \author Sergei Zharko <s.zharko@gsi.de> + +#pragma once + +#include "bmon/CalibrateSetup.h" +#include "bmon/HitfindSetup.h" + +#include <vector> + +namespace cbm::algo::bmon +{ + /// \struct HitfindQaParameters + /// \brief A structure to handle BMON QA parameters + struct HitfindQaParameters { + /// \struct Diamond + /// \brief A diamond representation + struct Diamond { + double timeRes{0.}; ///< Time resolution [ns] + double maxTimeDist{0.}; ///< Max time distance between digis in a hit [ns] + int32_t address{0}; ///< Address of a diamond + int32_t nChannels{0}; ///< Number of channels in a diamond + uint32_t deadStrips{0}; ///< A bit mask of dead strips + }; + + uint32_t selectionMask{0}; ///< A bitmask to distinguish different diamonds + std::vector<Diamond> diamonds{}; + + /// \brief Default constructor + HitfindQaParameters() = default; + + /// \brief Constructor + /// \param calSetup Calibration parameters + /// \param hitSetup Hitfinder parameters + HitfindQaParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup); + + /// \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 ((selectionMask & address) >> fSelectionBitsOffset); } + + private: + uint32_t fSelectionBitsOffset{0}; ///< Number of bits to the right from the first bit in the selection mask + }; +} // namespace cbm::algo::bmon -- GitLab