From c4af53763ad85c3ea75fb10e0aba07c733928392 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Tue, 4 Jun 2024 16:38:09 +0200
Subject: [PATCH] Offline QA:

1) possibility for giving a supporting message to the checked criteria, if they are not passed
2) cleaning-up of the CA input QA
---
 core/qa/CbmQaManager.cxx                      |   2 +-
 core/qa/CbmQaTable.cxx                        |  13 +-
 core/qa/CbmQaTable.h                          |   5 +-
 core/qa/CbmQaTask.cxx                         |   5 +-
 core/qa/CbmQaTask.h                           |   8 +-
 ...ks_config_mcbm_beam_2022_05_23_nickel.yaml |   5 +-
 reco/L1/qa/CbmCaInputQaBase.cxx               | 123 +++++++++++++++---
 reco/L1/qa/CbmCaInputQaBase.h                 |   7 +-
 reco/L1/qa/CbmCaInputQaSts.cxx                |   6 +-
 9 files changed, 135 insertions(+), 39 deletions(-)

diff --git a/core/qa/CbmQaManager.cxx b/core/qa/CbmQaManager.cxx
index 38de139993..0af606045d 100644
--- a/core/qa/CbmQaManager.cxx
+++ b/core/qa/CbmQaManager.cxx
@@ -48,7 +48,7 @@ void CbmQaManager::Finish()
       for (const auto& [entryName, flags] : mCheckList) {
         LOG(info) << '\t' << left << setw(40) << entryName << right << setw(10)
                   << (flags.fResult ? "\e[1;32mpassed\e[0m" : "\e[1;31mfailed\e[0m")
-                  << (flags.fStatus ? "" : " IGNORED");
+                  << (flags.fStatus ? "         " : " IGNORED ") << flags.fMsg;
         if (flags.fStatus) {
           fStatus &= flags.fResult;
         }
diff --git a/core/qa/CbmQaTable.cxx b/core/qa/CbmQaTable.cxx
index 78c4d7e3a5..578384a459 100644
--- a/core/qa/CbmQaTable.cxx
+++ b/core/qa/CbmQaTable.cxx
@@ -95,7 +95,7 @@ void CbmQaTable::SetRowName(Int_t iRow, const char* name) { TH2D::GetYaxis()->Se
 //
 //------------------------------------------------------------------------------------------------------------------------
 //
-std::string CbmQaTable::ToString(int prec) const
+std::string CbmQaTable::ToString(int prec, bool drawErr) const
 {
   std::stringstream aStream;
   aStream.setf(std::ios::fixed);
@@ -124,7 +124,16 @@ std::string CbmQaTable::ToString(int prec) const
     aStream << std::setw(fColWidth + 10) << std::setfill(' ') << entry << ' ';
 
     for (Int_t iCol = 0; iCol < fNcols; ++iCol) {
-      aStream << std::setw(fColWidth) << std::setfill(' ') << GetCell(iRow, iCol) << ' ';
+      std::stringstream cell;
+      cell.setf(std::ios::fixed);
+      cell.setf(std::ios::showpoint);
+      cell.setf(std::ios::left);
+      cell.precision(prec);
+      cell << GetCell(iRow, iCol);
+      if (drawErr) {
+        cell << "+/-" << GetCellError(iRow, iCol);
+      }
+      aStream << std::setw(fColWidth) << std::setfill(' ') << cell.str() << ' ';
     }
     aStream << '\n';
   }
diff --git a/core/qa/CbmQaTable.h b/core/qa/CbmQaTable.h
index 3f17907761..212ba5155f 100644
--- a/core/qa/CbmQaTable.h
+++ b/core/qa/CbmQaTable.h
@@ -33,8 +33,9 @@ class CbmQaTable : public TH2D {
   virtual ~CbmQaTable();
 
   /// Dumps table content into a string
-  /// \param  prec  Precision of numbers
-  std::string ToString(int prec) const;
+  /// \param  prec    Precision of numbers
+  /// \param  useErr  If true, the errors will be drawed together with the central values
+  std::string ToString(int prec, bool useErr = false) const;
 
   /// Dumps table content into a text file. File open mode is also controllable, for example, use
   /// mode = std::ios_base::app to append the table into an existing file
diff --git a/core/qa/CbmQaTask.cxx b/core/qa/CbmQaTask.cxx
index 471b070a8d..cd5058638d 100644
--- a/core/qa/CbmQaTask.cxx
+++ b/core/qa/CbmQaTask.cxx
@@ -308,4 +308,7 @@ void CbmQaTask::ReadCheckListFromConfig()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void CbmQaTask::StoreCheckResult(const std::string& tag, bool result) { fmCheckList[tag] = CheckFlags{result, false}; }
+void CbmQaTask::StoreCheckResult(const std::string& tag, bool result, const std::string& msg)
+{
+  fmCheckList[tag] = CheckFlags{msg, result, false};
+}
diff --git a/core/qa/CbmQaTask.h b/core/qa/CbmQaTask.h
index 6f2e6a8f7f..a2534dd2e5 100644
--- a/core/qa/CbmQaTask.h
+++ b/core/qa/CbmQaTask.h
@@ -49,8 +49,9 @@ class CbmQaTask : public FairTask, public CbmQaIO {
   ///
   /// If the status is set to false, the check result is ignored in the QA verdict.
   struct CheckFlags {
-    bool fResult = false;  ///< Check result storage
-    bool fStatus = false;  ///< Status of the check
+    std::string fMsg = "";     ///< Supporting message for the check
+    bool fResult     = false;  ///< Check result storage
+    bool fStatus     = false;  ///< Status of the check
   };
 
   /// \struct ObjectComparisonConfig
@@ -217,7 +218,8 @@ class CbmQaTask : public FairTask, public CbmQaIO {
   /// \brief Stores check flag to the check-list
   /// \param tag    The flag name
   /// \param result Check result
-  void StoreCheckResult(const std::string& tag, bool result);
+  /// \param msg    Supporting message (optional)
+  void StoreCheckResult(const std::string& tag, bool result, const std::string& msg = "");
 
  private:
   /// \brief De-initializes this task
diff --git a/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml b/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
index 1b1efd2be2..d71593d6cc 100644
--- a/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
+++ b/macro/qa/configs/qa_tasks_config_mcbm_beam_2022_05_23_nickel.yaml
@@ -100,7 +100,7 @@ qa:
       pull_t_station_0: false
       pull_x_station_%d: true
       pull_y_station_%d: true
-      pull_t_station_%d: true
+      pull_t_station_%d: false  # Time is not calibrated properly
   #
   #  CA INPUT QA: TOF
   #
@@ -114,9 +114,6 @@ qa:
       pull_t_station_%d: false
       pull_t_station_3: false
   CbmCaOutputQa:
-    specific:
-      testA: 42
-      testB: "hello"
     check_histograms:
       - name: "all/eff_pMC"
         point_to_point: { use: 1 }
diff --git a/reco/L1/qa/CbmCaInputQaBase.cxx b/reco/L1/qa/CbmCaInputQaBase.cxx
index 5cf956ed95..e06182e6c1 100644
--- a/reco/L1/qa/CbmCaInputQaBase.cxx
+++ b/reco/L1/qa/CbmCaInputQaBase.cxx
@@ -76,6 +76,8 @@ CbmCaInputQaBase<DetID>::CbmCaInputQaBase(const char* name, int verbose, bool is
 template<ca::EDetectorID DetID>
 void CbmCaInputQaBase<DetID>::Check()
 {
+  LOG(info) << "\n\n  ** Checking the " << fName << " **\n\n";
+
   int nSt = fpDetInterface->GetNtrackingStations();
 
   // **************************************************************
@@ -134,6 +136,8 @@ void CbmCaInputQaBase<DetID>::Check()
   //
   {
     bool res = true;
+    std::stringstream msgs;
+    msgs.precision(3);
     for (int iSt = 0; iSt < nSt; ++iSt) {
       int nHits = fvph_hit_station_delta_z[iSt]->GetEntries();
       if (!nHits) {
@@ -144,12 +148,20 @@ void CbmCaInputQaBase<DetID>::Check()
       int iBinMin = fvph_hit_station_delta_z[iSt]->FindBin(-fConfig.fMaxDiffZStHit);
       int iBinMax = fvph_hit_station_delta_z[iSt]->FindBin(+fConfig.fMaxDiffZStHit);
 
-      if (fvph_hit_station_delta_z[iSt]->Integral(iBinMin, iBinMax) < nHits) {
-        LOG_IF(error, fVerbose > 0) << fName << ": station " << iSt << " has mismatches in hit z-positions";
+      auto nHitsWithin = fvph_hit_station_delta_z[iSt]->Integral(iBinMin, iBinMax);
+      if (nHitsWithin < nHits) {
+        if (!msgs.str().empty()) {
+          msgs << ", ";
+        }
+        msgs << (static_cast<double>((nHits - nHitsWithin) * 100) / nHits) << "% (st. " << iSt << ")";
         res = false;
       }
     }
-    StoreCheckResult("station_position_hit_delta_z", res);
+    std::string msg = msgs.str().empty() ? "" : Form("Out of range z = +-%.2f cm: ", fConfig.fMaxDiffZStHit);
+    if (!msg.empty()) {
+      msg += msgs.str();
+    }
+    StoreCheckResult("station_position_hit_delta_z", res, msg);
   }
 
   // *******************************************************
@@ -175,7 +187,8 @@ void CbmCaInputQaBase<DetID>::Check()
         pEffTable->SetRowName(iSt, Form("station %d", iSt));
         pEffTable->SetCell(iSt, 0, eff);
         bool res = CheckRange("Hit finder efficiency in station " + std::to_string(iSt), eff, fConfig.fEffThrsh, 1.000);
-        StoreCheckResult(Form("hit_efficiency_station_%d", iSt), res);
+        std::string msg = (res ? "" : Form("efficiency = %f lower then threshold = %f", eff, fConfig.fEffThrsh));
+        StoreCheckResult(Form("hit_efficiency_station_%d", iSt), res, msg);
       }
       LOG(info) << '\n' << pEffTable->ToString(3);
     }
@@ -210,31 +223,53 @@ void CbmCaInputQaBase<DetID>::Check()
     // to unity. Here we allow a RMS of the pull distributions to be varied in a predefined range. If the RMS runs out
     // this range, QA task fails.
     {
-      auto* pPullsTable =
-        MakeQaObject<CbmQaTable>("vs Station/pulls_rms", "Pulls std. dev. values in different stations", nSt, 3);
-      pPullsTable->SetNamesOfCols({"Pull(x) sigm", "Pull(y) sigm", "Pull(t) sigm"});
-      pPullsTable->SetColWidth(20);
+      std::vector<CbmQaTable*> vpPullTables = {
+        // {x, y, t}
+        MakeQaObject<CbmQaTable>("vs Station/pulls_x", "x-coordinate pull quantities in diff. stations", nSt, 2),
+        MakeQaObject<CbmQaTable>("vs Station/pulls_y", "y-coordinate pull quantities in diff. stations", nSt, 2),
+        MakeQaObject<CbmQaTable>("vs Station/pulls_t", "Time pull quantities in diff. stations", nSt, 2)};
+
+      for (auto* pullTable : vpPullTables) {
+        pullTable->SetNamesOfCols({"mean", "std.dev."});
+        pullTable->SetColWidth(20);
+      }
 
-      for (int iSt = 0; iSt < nSt + 1; ++iSt) {
+      // NOTE: The table format is assumed: mean | 3.5 * mean err. | sigm | 3.5 * sigm err. |
+      auto DefinePullTableRow = [&](const TH1* h, CbmQaTable* table, int iSt) {
+        table->SetCell(iSt, 0, h->GetMean(), 3.5 * h->GetMeanError());
+        table->SetCell(iSt, 1, h->GetStdDev(), 3.5 * h->GetStdDevError());
+      };
 
+      for (int iSt = 0; iSt < nSt + 1; ++iSt) {
         // Fit pull distributions for nicer representation. Fit results are not used in further checks.
-
         cbm::qa::util::SetLargeStats(fvph_pull_x[iSt]);
         cbm::qa::util::SetLargeStats(fvph_pull_y[iSt]);
         cbm::qa::util::SetLargeStats(fvph_pull_t[iSt]);
 
+        for (auto* pullTable : vpPullTables) {
+          pullTable->SetRowName(iSt, (iSt == nSt ? "all stations" : Form("station %u", iSt)));
+        }
         // Check the pull quality
-        StoreCheckResult(Form("pull_x_station_%d", iSt), CheckRangePull(fvph_pull_x[iSt]));
-        StoreCheckResult(Form("pull_y_station_%d", iSt), CheckRangePull(fvph_pull_y[iSt]));
-        StoreCheckResult(Form("pull_t_station_%d", iSt), CheckRangePull(fvph_pull_t[iSt]));
-
-        pPullsTable->SetRowName(iSt, Form("station %d", iSt));
-        pPullsTable->SetCell(iSt, 0, fvph_pull_x[iSt]->GetStdDev());
-        pPullsTable->SetCell(iSt, 1, fvph_pull_y[iSt]->GetStdDev());
-        pPullsTable->SetCell(iSt, 2, fvph_pull_t[iSt]->GetStdDev());
+        {
+          auto [msg, status] = CheckRangePull(fvph_pull_x[iSt]);
+          StoreCheckResult(Form("pull_x_station_%d", iSt), status, msg);
+          DefinePullTableRow(fvph_pull_x[iSt], vpPullTables[0], iSt);
+        }
+        {
+          auto [msg, status] = CheckRangePull(fvph_pull_y[iSt]);
+          StoreCheckResult(Form("pull_y_station_%d", iSt), status, msg);
+          DefinePullTableRow(fvph_pull_y[iSt], vpPullTables[1], iSt);
+        }
+        {
+          auto [msg, status] = CheckRangePull(fvph_pull_t[iSt]);
+          StoreCheckResult(Form("pull_t_station_%d", iSt), status, msg);
+          DefinePullTableRow(fvph_pull_t[iSt], vpPullTables[2], iSt);
+        }
       }
 
-      LOG(info) << '\n' << pPullsTable->ToString(3);
+      for (auto* pullTable : vpPullTables) {
+        LOG(info) << '\n' << pullTable->ToString(3, true);
+      }
     }
   }  // McUsed
 
@@ -242,6 +277,56 @@ void CbmCaInputQaBase<DetID>::Check()
   LOG(info) << fMonitor.ToString();
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+template<ca::EDetectorID DetID>
+std::pair<std::string, bool> CbmCaInputQaBase<DetID>::CheckRangePull(TH1* h) const
+{
+  constexpr double factor = 3.5;
+  // Function to check overflow and underflow
+  constexpr auto Check = [&](double val, double err, double min, double max) -> std::pair<std::string, bool> {
+    std::stringstream msg;
+    bool res;
+    if (val + factor * err < min) {
+      msg << "underflow: " << val << " < " << min << " - " << factor << " x " << err;
+      res = false;
+    }
+    if (val - factor * err > max) {
+      msg << "overflow: " << val << " > " << max << " + " << factor << " x " << err;
+      res = false;
+    }
+    return std::make_pair(msg.str(), res);
+  };
+
+  std::pair<std::string, bool> ret = {"", true};
+  if (h->GetEntries() >= 10) {
+    // Mean check
+    {
+      auto [msg, res] = Check(h->GetMean(), h->GetMeanError(), -fConfig.fPullMeanThrsh, +fConfig.fPullMeanThrsh);
+      if (!msg.empty()) {
+        ret.first += "mean ";
+        ret.first += msg;
+        ret.second = res;
+      }
+    }
+    // Sigma check
+    {
+      auto [msg, res] =
+        Check(h->GetStdDev(), h->GetStdDevError(), 1. - fConfig.fPullWidthThrsh, 1. + fConfig.fPullWidthThrsh);
+      if (!msg.empty()) {
+        if (!ret.first.empty()) {
+          ret.first += "; ";
+        }
+        ret.first += "std.dev. ";
+        ret.first += msg;
+        ret.second = res;
+      }
+    }
+  }
+  return ret;
+}
+
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 template<ca::EDetectorID DetID>
diff --git a/reco/L1/qa/CbmCaInputQaBase.h b/reco/L1/qa/CbmCaInputQaBase.h
index 46ee3b7bcf..4fe628abc9 100644
--- a/reco/L1/qa/CbmCaInputQaBase.h
+++ b/reco/L1/qa/CbmCaInputQaBase.h
@@ -130,11 +130,8 @@ class CbmCaInputQaBase : public CbmQaTask {
   InitStatus InitQa() override;
 
   /// \brief Checks ranges for mean and standard deviation
-  /// \return  False, if variable exits the range
-  bool CheckRangePull(TH1* h)
-  {
-    return CbmQaTask::CheckRange(h, fConfig.fPullMeanThrsh, 1. - fConfig.fPullWidthThrsh, 1. + fConfig.fPullWidthThrsh);
-  }
+  /// \return  String with an error message: empty string is equivalent to success
+  std::pair<std::string, bool> CheckRangePull(TH1* h) const;
 
   /// \brief Returns a pointer to current hit QA data object
   ///
diff --git a/reco/L1/qa/CbmCaInputQaSts.cxx b/reco/L1/qa/CbmCaInputQaSts.cxx
index c8bb59e5b6..4d8e2dfe19 100644
--- a/reco/L1/qa/CbmCaInputQaSts.cxx
+++ b/reco/L1/qa/CbmCaInputQaSts.cxx
@@ -61,10 +61,12 @@ void CbmCaInputQaSts::Check()
   if (IsMCUsed()) {
     for (int idig = 0; idig <= fkMaxDigisInClusterForPulls; idig++) {
       cbm::qa::util::SetLargeStats(fvph_pull_u_Ndig[idig]);
-      StoreCheckResult(Form("pull_u_%d_digis", idig), CheckRangePull(fvph_pull_u_Ndig[idig]));
+      auto [msgU, resU] = CheckRangePull(fvph_pull_u_Ndig[idig]);
+      StoreCheckResult(Form("pull_u_%d_digis", idig), resU, msgU);
 
       cbm::qa::util::SetLargeStats(fvph_pull_v_Ndig[idig]);
-      StoreCheckResult(Form("pull_v_%d_digis", idig), CheckRangePull(fvph_pull_v_Ndig[idig]));
+      auto [msgV, resV] = CheckRangePull(fvph_pull_v_Ndig[idig]);
+      StoreCheckResult(Form("pull_v_%d_digis", idig), resV, msgV);
     }
   }  // McUsed
 }
-- 
GitLab