From 44db5a9d402ad7fc2d03d217be76baeb6c60c862 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Tue, 30 Aug 2022 19:27:46 +0200
Subject: [PATCH] L1: added input data IO functionality

---
 reco/L1/CbmL1.cxx                  | 115 ++++++++++++++++++++---------
 reco/L1/CbmL1.h                    |   8 +-
 reco/L1/CbmL1ReadEvent.cxx         |   3 +
 reco/L1/L1Algo/L1Hit.h             |  22 ++++++
 reco/L1/L1Algo/L1IODataManager.cxx |  46 ++++++++++++
 reco/L1/L1Algo/L1IODataManager.h   |   8 ++
 reco/L1/L1Algo/L1InputData.h       |  15 +++-
 reco/L1/L1Algo/L1Vector.h          |  14 ++++
 8 files changed, 191 insertions(+), 40 deletions(-)

diff --git a/reco/L1/CbmL1.cxx b/reco/L1/CbmL1.cxx
index d8e8386fb4..27655f2603 100644
--- a/reco/L1/CbmL1.cxx
+++ b/reco/L1/CbmL1.cxx
@@ -425,30 +425,6 @@ InitStatus CbmL1::Init()
   fInitManager.SetNstations(L1DetectorID::kTrd, fNTrdStationsGeom);
   fInitManager.SetNstations(L1DetectorID::kTof, fNTofStationsGeom);
 
-  {
-    if (fSTAPDataMode % 2 == 1) {  // 1,3
-      LOG(fatal) << "CbmL1::Init: geo vector was removed, currently data cannot be written to a text-file";
-      // TODO: Rewrite parameters i/o into L1InitManager (S.Zharko, 12.05.2022)
-      //WriteSTAPGeoData(geo);
-    };
-    //if(fSTAPDataMode >= 2){ // 2,3
-    //  int ind2, ind = geo.size();
-    //  ReadSTAPGeoData(geo, ind2);
-    //  if (ind2 != ind)  LOG(error) << "-E- CbmL1: Read geometry from file " << fSTAPDataDir + "geo_algo.txt was NOT successful.";
-    //};
-  }
-
-  if (fSTAPDataMode >= 2) {  // 2,3
-    LOG(fatal) << "CbmL1::Init: geo vector was removed, currently data cannot be read from a text-file. "
-               << "Please, run CbmL1 task with STAPDataMode option < 2";
-    // TODO: Rewrite parameters i/o into L1InitManager (S.Zharko, 12.05.2022)
-    //int ind2, ind = geo.size();
-    //ReadSTAPGeoData(geo, ind2);
-    //if (ind2 != ind)
-    //  LOG(error) << "-E- CbmL1: Read geometry from file " << fSTAPDataDir + "geo_algo.txt was NOT successful.";
-  }
-
-
   /****************************
    ** Material budget input  **
    ****************************/
@@ -952,10 +928,6 @@ void CbmL1::Reconstruct(CbmEvent* event)
       FstHitinTs  = 0;
     }
 
-    if (fSTAPDataMode >= 2) {  // 2,3
-      LOG(fatal) << "L1: Sorry, at the moment standalone tracking module is unavailable. This functionality will be "
-                 << "reimplemented soon.";
-    }
 
     // ----- Read data from branches and send data from IODataManager to L1Algo ----------------------------------------
     ReadEvent(TsStart, TsLength, TsOverlap, FstHitinTs, areDataLeft, event);
@@ -1302,36 +1274,109 @@ void CbmL1::IdealTrackFinder()
 
 /// -----   STandAlone Package service-functions  -----------------------------
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
 void CbmL1::WriteSTAPGeoData(const L1Vector<float>& /*geo_*/)
 {
   LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
 }
 
-void CbmL1::WriteSTAPAlgoData()  // must be called after ReadEvent
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmL1::WriteAlgoInputData()  // must be called after ReadEvent
 {
-  LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
+  // Check if output directory exists
+  if (!boost::filesystem::exists(fSTAPDataDir.Data())) {
+    LOG(warn) << "CbmL1: directory " << fSTAPDataDir.Data()
+              << " (full path: " << boost::filesystem::system_complete(fSTAPDataDir.Data()).string()
+              << ") for writing L1AlgoData object does not exist";
+    fSTAPDataDir = ".";
+  }
+
+  if (!boost::filesystem::is_directory(fSTAPDataDir.Data())) {
+    LOG(warn) << "CbmL1: path " << fSTAPDataDir.Data()
+              << " (full path: " << boost::filesystem::system_complete(fSTAPDataDir.Data()).string()
+              << ") is not a directory";
+    fSTAPDataDir = ".";
+  }
+
+  // Define output directory
+  std::string hitsDir = (fSTAPDataDir + "/tracking_input_hits").Data();
+  if (!boost::filesystem::exists(hitsDir)) { boost::filesystem::create_directories(hitsDir); }
+
+  // Get filename
+  static int iEvent         = 0;
+  boost::filesystem::path p = (FairRunAna::Instance()->GetUserOutputFileName()).Data();
+  std::string prefix        = p.filename().string();
+  std::string suffix        = "reco.root";
+  prefix.erase(prefix.find("reco.root"));
+  std::string filename = hitsDir + "/" + prefix + "event" + std::to_string(iEvent) + ".L1InputData.dat";
+  ++iEvent;
+
+  // Write file
+  L1_SHOW(filename);
+  fIODataManager.WriteInputData(filename);
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
 void CbmL1::WriteSTAPPerfData()  // must be called after ReadEvent
 {
   LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
-}  // void CbmL1::WriteSTAPPerfData()
+}
 
-void CbmL1::ReadSTAPGeoData(L1Vector<fscal>& /*geo_*/, int& /*size*/)
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmL1::ReadSTAPGeoData()
 {
   LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
-}  // void CbmL1::ReadSTAPGeoData(void* geo_, int &size)
+}
 
-void CbmL1::ReadSTAPAlgoData()
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void CbmL1::ReadAlgoInputData()
 {
-  LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
+  // Check if output directory exists
+  if (!boost::filesystem::exists(fSTAPDataDir.Data())) {
+    LOG(warn) << "CbmL1: directory " << fSTAPDataDir.Data()
+              << " (full path: " << boost::filesystem::system_complete(fSTAPDataDir.Data()).string()
+              << ") for reading L1AlgoData object does not exist";
+    fSTAPDataDir = ".";
+  }
+
+  if (!boost::filesystem::is_directory(fSTAPDataDir.Data())) {
+    LOG(warn) << "CbmL1: path " << fSTAPDataDir.Data()
+              << " (full path: " << boost::filesystem::system_complete(fSTAPDataDir.Data()).string()
+              << ") is not a directory";
+    fSTAPDataDir = ".";
+  }
+
+  // Define output directory
+  std::string hitsDir = fSTAPDataDir.Data();
+
+  // Get filename
+  static int iEvent         = 0;
+  boost::filesystem::path p = (FairRunAna::Instance()->GetUserOutputFileName()).Data();
+  std::string prefix        = p.filename().string();
+  std::string suffix        = "reco.root";
+  prefix.erase(prefix.find("reco.root"));
+  std::string filename = hitsDir + "/" + prefix + "event" + std::to_string(iEvent) + ".L1InputData.dat";
+  ++iEvent;
+
+  // Write file
+  L1_SHOW(filename);
+  fIODataManager.ReadInputData(filename);
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
 void CbmL1::ReadSTAPPerfData()
 {
   LOG(fatal) << "CbmL1: Running in standalone mode is not available at the moment. It will be updated soon...";
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
 void CbmL1::WriteSIMDKFData()
 {
   // TODO: Must be totally reimplemented (S.Zharko)
diff --git a/reco/L1/CbmL1.h b/reco/L1/CbmL1.h
index eaf828052b..db153bfec3 100644
--- a/reco/L1/CbmL1.h
+++ b/reco/L1/CbmL1.h
@@ -360,11 +360,11 @@ private:
 
   /// STandAlone Package service-functions
   void WriteSTAPGeoData(const L1Vector<float>& geo);  // create geo_algo.dat
-  void WriteSTAPAlgoData();                           // create data_algo.dat
+  void WriteAlgoInputData();                          // create data_algo.dat
   void WriteSTAPPerfData();                           // create data_perfo.dat
   //void ReadSTAPGeoData(L1Vector<float> geo, int &size);
-  void ReadSTAPGeoData(L1Vector<float>& geo, int& size);
-  void ReadSTAPAlgoData();
+  void ReadSTAPGeoData();
+  void ReadAlgoInputData();
   void ReadSTAPPerfData();
   /// SIMD KF Banchmark service-functions
   void WriteSIMDKFData();
@@ -387,6 +387,8 @@ private:
 
   inline Double_t dFEI(int file, int event, int idx) { return (1000 * idx) + file + (0.0001 * event); }
 
+private:
+  std::string fInputDataFilename = "";  ///< File name to read/write input hits
 
   // ***************************
   // ** Member variables list **
diff --git a/reco/L1/CbmL1ReadEvent.cxx b/reco/L1/CbmL1ReadEvent.cxx
index f781e68b0e..ede3970530 100644
--- a/reco/L1/CbmL1ReadEvent.cxx
+++ b/reco/L1/CbmL1ReadEvent.cxx
@@ -1185,6 +1185,9 @@ void CbmL1::ReadEvent(float& TsStart, float& TsLength, float& /*TsOverlap*/, int
   if (fVerbose >= 1) cout << "ReadEvent: mvd and sts are saved." << endl;
 
   // ----- Send data from IODataManager to L1Algo --------------------------------------------------------------------
+  if (1 == fSTAPDataMode) { WriteAlgoInputData(); }
+  if (2 == fSTAPDataMode) { ReadAlgoInputData(); }
+  // TODO: SZh: If we read data from file, we don't need to collect them above. This should be addressed
   fIODataManager.SendInputData(fpAlgo);
 
   if (fPerformance) {
diff --git a/reco/L1/L1Algo/L1Hit.h b/reco/L1/L1Algo/L1Hit.h
index 4568b1eda8..acdea6e3c4 100644
--- a/reco/L1/L1Algo/L1Hit.h
+++ b/reco/L1/L1Algo/L1Hit.h
@@ -12,6 +12,8 @@
 #ifndef L1Hit_h
 #define L1Hit_h
 
+#include <boost/serialization/access.hpp>
+
 #include "L1Constants.h"
 
 using L1HitIndex_t   = unsigned /*short*/ int;  ///< Index of L1Hit
@@ -23,6 +25,8 @@ using L1StripIndex_t = unsigned /*short*/ int;  ///< Index of the station strip
 /// Note: V is a transverse coordinate of the hit in the axis perpendicular to the back strip
 ///
 class /*alignas(L1Constants::misc::kAlignment)*/ L1Hit {
+  friend class boost::serialization::access;
+
 public:
   L1StripIndex_t f {0};  ///< front hit key index
   L1StripIndex_t b {0};  ///< back hit key index
@@ -40,6 +44,24 @@ public:
   int ID   = 0;    ///< index of hit before hits sorting
   int iSt  = 0;    ///< index of station in the active stations array
   // TODO: Test speed penalty of using iSt index
+
+private:
+  /// Serialization method, used to save L1Hit objects into binary or text file in a defined order
+  template<class Archive>
+  void serialize(Archive& ar, const unsigned int /*version*/)
+  {
+    ar& f;
+    ar& b;
+    ar& u;
+    ar& v;
+    ar& t;
+    ar& z;
+    ar& du;
+    ar& dv;
+    ar& dt;
+    ar& ID;
+    ar& iSt;
+  }
 };
 
 #endif
diff --git a/reco/L1/L1Algo/L1IODataManager.cxx b/reco/L1/L1Algo/L1IODataManager.cxx
index 2b7f7d5682..86bc8a7662 100644
--- a/reco/L1/L1Algo/L1IODataManager.cxx
+++ b/reco/L1/L1Algo/L1IODataManager.cxx
@@ -9,6 +9,11 @@
 
 #include "L1IODataManager.h"
 
+#include <boost/archive/binary_iarchive.hpp>
+#include <boost/archive/binary_oarchive.hpp>
+
+#include <fstream>
+
 #include "L1Algo.h"
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -32,6 +37,28 @@ bool L1IODataManager::SendInputData(L1Algo* pAlgo)
   return false;
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void L1IODataManager::ReadInputData(const std::string& fileName)
+{
+  // Reset input data object
+  this->ResetInputData();
+  LOG(info) << "L1: Input data will be read from file \"" << fileName << "\"";
+
+  // Open input binary file
+  std::ifstream ifs(fileName, std::ios::binary);
+  if (!ifs) { LOG(fatal) << "L1: input data reader: data file \"" << fileName << "\" was not found"; }
+
+  // Get L1InputData object
+  try {
+    boost::archive::binary_iarchive ia(ifs);
+    ia >> fInputData;
+  }
+  catch (const std::exception&) {
+    LOG(fatal) << "L1: input data reader: data file \"" << fileName << "\" has incorrect data format or was corrupted";
+  }
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void L1IODataManager::ResetInputData() noexcept
@@ -66,3 +93,22 @@ void L1IODataManager::SetStartStopHitIndexes()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
+void L1IODataManager::WriteInputData(const std::string& fileName) const
+{
+  // Check current data object for consistency
+  if (!CheckInputData<L1Constants::control::kInputDataQaLevel>()) {
+    LOG(error) << "L1: input data writer: attempt to write invalid input data object to file \"" << fileName << "\"";
+    return;
+  }
+
+  // Open output binary file
+  std::ofstream ofs(fileName, std::ios::binary);
+  if (!ofs) {
+    LOG(error) << "L1: input data writer: failed opening file \"" << fileName << " for writing input data\"";
+    return;
+  }
+
+  // Serialize L1InputData object and write
+  boost::archive::binary_oarchive oa(ofs);
+  oa << fInputData;
+}
diff --git a/reco/L1/L1Algo/L1IODataManager.h b/reco/L1/L1Algo/L1IODataManager.h
index 98fa077937..a59dfcaa30 100644
--- a/reco/L1/L1Algo/L1IODataManager.h
+++ b/reco/L1/L1Algo/L1IODataManager.h
@@ -49,6 +49,10 @@ public:
   /// Move assignment operator
   L1IODataManager& operator=(L1IODataManager&& other) = delete;
 
+  /// Reads input data object from boost-serialized binary file
+  /// \param  fileName  Name of input file
+  void ReadInputData(const std::string& fileName);
+
   /// Reserve number of hits
   /// \param  nHits  Number of hits to be stored
   /// \note   If one does not call this method, the underlying vector of hits will be filled with the time penalty
@@ -70,6 +74,10 @@ public:
   /// \return Success flag
   bool SendInputData(L1Algo* pAlgo);
 
+  /// Writes input data object to boost-serialized binary file
+  /// \param  fileName  Name of input file
+  void WriteInputData(const std::string& fileName) const;
+
 private:
   /// Sets the start and stop indexes vs. station index
   void SetStartStopHitIndexes();
diff --git a/reco/L1/L1Algo/L1InputData.h b/reco/L1/L1Algo/L1InputData.h
index 88a204d045..d7c2569110 100644
--- a/reco/L1/L1Algo/L1InputData.h
+++ b/reco/L1/L1Algo/L1InputData.h
@@ -10,13 +10,15 @@
 #ifndef L1InputData_h
 #define L1InputData_h 1
 
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/array.hpp>
+
 #include <array>
 
 #include "L1Constants.h"
 #include "L1Hit.h"
 #include "L1Vector.h"
 
-
 /// Class L1InputData represents a block of the input data to the L1 tracking algorithm per event or time slice.
 /// Filling of the L1InputData is carried out with L1IODataManager class
 ///
@@ -27,7 +29,7 @@ public:
   // **************************
 
   friend class L1IODataManager;  ///< Class which fills the L1InputData object for each event or time slice
-
+  friend class boost::serialization::access;
 
   // ***************************
   // ** Member functions list **
@@ -85,6 +87,15 @@ private:
   /// Swap method
   void Swap(L1InputData& other) noexcept;
 
+  /// Data serialization method
+  template<class Archive>
+  void serialize(Archive& ar, const unsigned int /*versino*/)
+  {
+    ar& fvHits;
+    ar& fvStartHitIndexes;
+    ar& fvStopHitIndexes;
+    ar& fNhitKeys;
+  }
 
   // ***************************
   // ** Member variables list **
diff --git a/reco/L1/L1Algo/L1Vector.h b/reco/L1/L1Algo/L1Vector.h
index 780a9cf125..5e5654afb8 100644
--- a/reco/L1/L1Algo/L1Vector.h
+++ b/reco/L1/L1Algo/L1Vector.h
@@ -14,6 +14,11 @@
 #ifndef FAST_CODE
 #include <FairLogger.h>
 #endif
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+
 #include <sstream>
 
 /// L1Vector class is a wrapper around std::vector.
@@ -30,6 +35,8 @@
 
 template<class T>
 class L1Vector : private std::vector<T> {
+  friend class boost::serialization::access;
+
 public:
   typedef std::vector<T> Tbase;
 
@@ -239,6 +246,13 @@ private:
   using Tbase::assign;  // use reset() instead
   using Tbase::at;
   using Tbase::resize;
+
+  template<class Archive>
+  void serialize(Archive& ar, const unsigned int /*version*/)
+  {
+    ar& boost::serialization::base_object<Tbase>(*this);
+    ar& fName;
+  }
 };
 
 ///
-- 
GitLab