From 5082cb206ed7e46e9a1ada62ded9689c5608712d Mon Sep 17 00:00:00 2001
From: Sylvester Joosten <sjoosten@anl.gov>
Date: Sat, 21 Aug 2021 22:18:39 +0000
Subject: [PATCH] Added algorithm to merge cluster info (e.g. imaging and scfi
 info)

---
 JugBase/JugBase/UniqueID.h                    |   3 +-
 JugReco/CMakeLists.txt                        |   1 +
 JugReco/src/components/CalorimeterHitReco.cpp |   4 +-
 .../CalorimeterHitsEtaPhiProjector.cpp        |   4 +-
 .../src/components/CalorimeterHitsMerger.cpp  |   4 +-
 JugReco/src/components/ClusterRecoCoG.cpp     |   4 +-
 .../EnergyPositionClusterMerger.cpp           | 130 ++++++++++++++++++
 JugReco/src/components/ImagingClusterReco.cpp |   4 +-
 JugReco/src/components/ImagingPixelMerger.cpp |   4 +-
 JugReco/src/components/ImagingPixelReco.cpp   |   4 +-
 .../src/components/PhotoMultiplierReco.cpp    |   4 +-
 JugReco/src/components/SimpleClustering.cpp   |   4 +-
 .../components/TrackerHitReconstruction.cpp   |   4 +-
 13 files changed, 153 insertions(+), 21 deletions(-)
 create mode 100644 JugReco/src/components/EnergyPositionClusterMerger.cpp

diff --git a/JugBase/JugBase/UniqueID.h b/JugBase/JugBase/UniqueID.h
index 3c9989d2..977249ff 100644
--- a/JugBase/JugBase/UniqueID.h
+++ b/JugBase/JugBase/UniqueID.h
@@ -6,6 +6,7 @@
 //
 // Both in function and mixin form (latter is useful for algorithms)
 
+#include <cstdint>
 #include <functional>
 #include <limits>
 #include <string>
@@ -20,7 +21,7 @@ template <class Integer> Integer uniqueID(const std::string& s) {
   return static_cast<Integer>(fullID & max);
 }
 
-template <class Integer> class AlgorithmIDMixin {
+template <class Integer = int32_t> class AlgorithmIDMixin {
 public:
   AlgorithmIDMixin(const std::string& name, MsgStream& out) : m_id{uniqueID<Integer>(name)} {
     out << "Unique ID associated with '" << name << "': " << m_id << "\n";
diff --git a/JugReco/CMakeLists.txt b/JugReco/CMakeLists.txt
index 68acc233..b90538d8 100644
--- a/JugReco/CMakeLists.txt
+++ b/JugReco/CMakeLists.txt
@@ -20,6 +20,7 @@ gaudi_add_module(JugRecoPlugins
   src/components/CrystalEndcapsReco.cpp 
   src/components/EcalTungstenSamplingReco.cpp
   src/components/EcalTungstenSamplingCluster.cpp
+  src/components/EnergyPositionClusterMerger.cpp
   src/components/ClusterRecoCoG.cpp 
   src/components/PhotoMultiplierReco.cpp 
   src/components/PhotoRingClusters.cpp 
diff --git a/JugReco/src/components/CalorimeterHitReco.cpp b/JugReco/src/components/CalorimeterHitReco.cpp
index 31ebdc89..00da050a 100644
--- a/JugReco/src/components/CalorimeterHitReco.cpp
+++ b/JugReco/src/components/CalorimeterHitReco.cpp
@@ -37,7 +37,7 @@ namespace Jug::Reco {
    * Reconstruct digitized outputs, paired with Jug::Digi::CalorimeterHitDigi
    * \ingroup reco
    */
-  class CalorimeterHitReco : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class CalorimeterHitReco : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
 
     // length unit from dd4hep, should be fixed
@@ -81,7 +81,7 @@ namespace Jug::Reco {
 
     CalorimeterHitReco(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/CalorimeterHitsEtaPhiProjector.cpp b/JugReco/src/components/CalorimeterHitsEtaPhiProjector.cpp
index cbfabc37..953e209b 100644
--- a/JugReco/src/components/CalorimeterHitsEtaPhiProjector.cpp
+++ b/JugReco/src/components/CalorimeterHitsEtaPhiProjector.cpp
@@ -57,7 +57,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class CalorimeterHitsEtaPhiProjector : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class CalorimeterHitsEtaPhiProjector : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     Gaudi::Property<std::vector<double>>        u_gridSizes{this, "gridSizes", {0.001, 0.001*rad}};
     DataHandle<eic::CalorimeterHitCollection>   m_inputHitCollection{
@@ -69,7 +69,7 @@ namespace Jug::Reco {
 
     CalorimeterHitsEtaPhiProjector(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/CalorimeterHitsMerger.cpp b/JugReco/src/components/CalorimeterHitsMerger.cpp
index 156c7206..872a7d9f 100644
--- a/JugReco/src/components/CalorimeterHitsMerger.cpp
+++ b/JugReco/src/components/CalorimeterHitsMerger.cpp
@@ -43,7 +43,7 @@ namespace Jug::Reco {
    *
    *  \ingroup reco
    */
-  class CalorimeterHitsMerger : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class CalorimeterHitsMerger : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     Gaudi::Property<std::string> m_geoSvcName{this, "geoServiceName", "GeoSvc"};
     Gaudi::Property<std::string> m_readout{this, "readoutClass", ""};
@@ -61,7 +61,7 @@ namespace Jug::Reco {
 
     CalorimeterHitsMerger(const std::string& name, ISvcLocator* svcLoc)
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/ClusterRecoCoG.cpp b/JugReco/src/components/ClusterRecoCoG.cpp
index 3cb873cb..0b9d58d6 100644
--- a/JugReco/src/components/ClusterRecoCoG.cpp
+++ b/JugReco/src/components/ClusterRecoCoG.cpp
@@ -63,7 +63,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class ClusterRecoCoG : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class ClusterRecoCoG : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     Gaudi::Property<double>                   m_sampFrac{this, "samplingFraction", 1.0};
     Gaudi::Property<double>                   m_logWeightBase{this, "logWeightBase", 3.6};
@@ -85,7 +85,7 @@ namespace Jug::Reco {
 
     ClusterRecoCoG(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHits, "");
       declareProperty("inputProtoClusterCollection", m_inputProto, "");
diff --git a/JugReco/src/components/EnergyPositionClusterMerger.cpp b/JugReco/src/components/EnergyPositionClusterMerger.cpp
new file mode 100644
index 00000000..5f7af366
--- /dev/null
+++ b/JugReco/src/components/EnergyPositionClusterMerger.cpp
@@ -0,0 +1,130 @@
+#include <limits>
+// Gaudi
+#include "Gaudi/Property.h"
+#include "GaudiAlg/GaudiAlgorithm.h"
+#include "GaudiAlg/GaudiTool.h"
+#include "GaudiAlg/Transformer.h"
+#include "GaudiKernel/PhysicalConstants.h"
+
+#include "JugBase/DataHandle.h"
+#include "JugBase/UniqueID.h"
+
+// Event Model related classes
+#include "eicd/ClusterCollection.h"
+#include "eicd/MergedClusterRelationsCollection.h"
+
+using namespace Gaudi::Units;
+
+namespace Jug::Reco {
+
+/** Simple algorithm to merge the energy measurement from cluster1 with the position
+ * measurement of cluster2 (in case matching clusters are found). If not, it will
+ * propagate the raw cluster from cluster1 or cluster2
+ *
+ * Matching occurs based on the cluster phi, z and E variables, with tolerances
+ * defined in the options file. A negative tolerance effectively disables
+ * a check. The energy tolerance is defined as a relative number (e.g. .1)
+ *
+ * In case of ambiguity the closest cluster is merged.
+ *
+ * \ingroup reco
+ */
+class EnergyPositionClusterMerger : public GaudiAlgorithm, public AlgorithmIDMixin<> {
+public:
+  // Input
+  DataHandle<eic::ClusterCollection> m_energyClusters{"energyClusters", Gaudi::DataHandle::Reader, this};
+  DataHandle<eic::ClusterCollection> m_positionClusters{"positionClusters", Gaudi::DataHandle::Reader, this};
+  // Output
+  DataHandle<eic::ClusterCollection> m_outputClusters{"outputClusters", Gaudi::DataHandle::Writer, this};
+  DataHandle<eic::MergedClusterRelationsCollection> m_relations{"outputCluster", Gaudi::DataHandle::Writer,
+                                                                this}; // namespace Jug::Reco
+  // Negative values mean the tolerance check is disabled
+  Gaudi::Property<double> m_zToleranceUnits{this, "zTolerance", -1 * cm};
+  Gaudi::Property<double> m_phiToleranceUnits{this, "phiTolerance", 20 * degree};
+  Gaudi::Property<double> m_energyRelTolerance{this, "energyRelTolerance", 0.3};
+  // Unitless (GeV/mm/ns/rad) versions of these tolerances
+  double m_zTolerance;
+  double m_phiTolerance;
+
+public:
+  EnergyPositionClusterMerger(const std::string& name, ISvcLocator* svcLoc)
+      : GaudiAlgorithm(name, svcLoc), AlgorithmIDMixin(name, info()) {
+    declareProperty("energyClusters", m_energyClusters, "Cluster collection with good energy precision");
+    declareProperty("positionClusters", m_positionClusters, "Cluster collection with good position precision");
+  }
+
+  StatusCode initialize() override {
+    m_zTolerance   = m_zToleranceUnits / mm;
+    m_phiTolerance = m_phiTolerance / rad;
+    return StatusCode::SUCCESS;
+  }
+
+  StatusCode execute() override {
+    // input
+    const auto& e_clus   = *(m_energyClusters.get());
+    const auto& pos_clus = *(m_positionClusters.get());
+    // output
+    auto& merged    = *(m_outputClusters.createAndPut());
+    auto& relations = *(m_relations.createAndPut());
+
+    std::vector<bool> consumed(e_clus.size(), false);
+
+    int idx = 0; // merged cluster ID
+    // use position clusters as starting point
+    for (const auto& pc : pos_clus) {
+      // check if we find a good match
+      int best_match    = -1;
+      double best_delta = std::numeric_limits<double>::max();
+      for (size_t ie = 0; ie < e_clus.size(); ++ie) {
+        if (consumed[ie]) {
+          continue;
+        }
+        const auto& ec = e_clus[ie];
+        // 1. stop if not within tolerance
+        if ((m_energyRelTolerance > 0 &&
+             (ec.energy() <= 0 || fabs((pc.energy() - ec.energy()) / ec.energy()) > m_energyRelTolerance)) ||
+            (m_zTolerance > 0 && fabs(pc.position().z - ec.position().z) > m_zTolerance) ||
+            (m_phiTolerance > 0 && fabs(pc.position().phi() - ec.position().phi()) > m_phiTolerance)) {
+          continue;
+        }
+        // --> if we get here, we have a match within tolerance. Now treat the case
+        //     where we have multiple matches. In this case take the one with the closest
+        //     energies.
+        // 2. best match?
+        const double delta = fabs(pc.energy() - ec.energy());
+        if (delta < best_delta) {
+          best_delta = delta;
+          best_match = ie;
+        }
+      }
+      // Create a merged cluster if we find a good match
+      if (best_match >= 0) {
+        const auto& ec = e_clus[best_match];
+        auto new_clus  = merged.create();
+        new_clus.ID(idx++);
+        new_clus.source(algorithmID());
+        new_clus.energy(ec.energy());
+        new_clus.energyError(ec.energyError());
+        new_clus.time(pc.time());
+        new_clus.nhits(pc.nhits() + ec.nhits());
+        new_clus.position(pc.position());
+        new_clus.positionError(pc.positionError());
+        new_clus.radius(pc.radius());
+        new_clus.skewness(pc.skewness());
+        auto rel = relations.create();
+        rel.clusterID(new_clus.ID());
+        rel.size(2);
+        rel.parent()[0] = {pc.source(), pc.ID()};
+        rel.parent()[1] = {ec.source(), ec.ID()};
+        // label our energy cluster as consumed
+        consumed[best_match] = true;
+      }
+    }
+    // That's all!
+
+    return StatusCode::SUCCESS;
+  }
+};
+DECLARE_COMPONENT(EnergyPositionClusterMerger)
+
+} // namespace Jug::Reco
diff --git a/JugReco/src/components/ImagingClusterReco.cpp b/JugReco/src/components/ImagingClusterReco.cpp
index 29c7c255..66611c8b 100644
--- a/JugReco/src/components/ImagingClusterReco.cpp
+++ b/JugReco/src/components/ImagingClusterReco.cpp
@@ -46,7 +46,7 @@ namespace Jug::Reco {
  *
  *  \ingroup reco
  */
-class ImagingClusterReco : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+class ImagingClusterReco : public GaudiAlgorithm, AlgorithmIDMixin<> {
 public:
   Gaudi::Property<double> m_sampFrac{this, "samplingFraction", 1.0};
   Gaudi::Property<int> m_trackStopLayer{this, "trackStopLayer", 9};
@@ -63,7 +63,7 @@ public:
 
   ImagingClusterReco(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info()) {
+      , AlgorithmIDMixin<>(name, info()) {
     declareProperty("inputProtoClusterCollection", m_inputProtoClusterCollection, "");
     declareProperty("inputHitCollection", m_inputHitCollection, "");
     declareProperty("outputLayerCollection", m_outputLayerCollection, "");
diff --git a/JugReco/src/components/ImagingPixelMerger.cpp b/JugReco/src/components/ImagingPixelMerger.cpp
index da8cea79..102c2237 100644
--- a/JugReco/src/components/ImagingPixelMerger.cpp
+++ b/JugReco/src/components/ImagingPixelMerger.cpp
@@ -56,7 +56,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class ImagingPixelMerger : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class ImagingPixelMerger : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     Gaudi::Property<int>                    m_nHits{this, "numberOfHits", 20};
     Gaudi::Property<int>                    m_nLayers{this, "numberOfLayers", 20};
@@ -69,7 +69,7 @@ namespace Jug::Reco {
 
     ImagingPixelMerger(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/ImagingPixelReco.cpp b/JugReco/src/components/ImagingPixelReco.cpp
index e3945f39..74ebe43b 100644
--- a/JugReco/src/components/ImagingPixelReco.cpp
+++ b/JugReco/src/components/ImagingPixelReco.cpp
@@ -36,7 +36,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class ImagingPixelReco : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class ImagingPixelReco : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     // geometry service
     Gaudi::Property<std::string> m_geoSvcName{this, "geoServiceName", "GeoSvc"};
@@ -71,7 +71,7 @@ namespace Jug::Reco {
 
     ImagingPixelReco(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/PhotoMultiplierReco.cpp b/JugReco/src/components/PhotoMultiplierReco.cpp
index eb4b44db..3183b4f1 100644
--- a/JugReco/src/components/PhotoMultiplierReco.cpp
+++ b/JugReco/src/components/PhotoMultiplierReco.cpp
@@ -40,7 +40,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class PhotoMultiplierReco : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class PhotoMultiplierReco : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     DataHandle<eic::RawPMTHitCollection> m_inputHitCollection{"inputHitCollection", Gaudi::DataHandle::Reader, this};
     DataHandle<eic::PMTHitCollection>    m_outputHitCollection{"outputHitCollection", Gaudi::DataHandle::Writer, this};
@@ -54,7 +54,7 @@ namespace Jug::Reco {
     // ill-formed: using GaudiAlgorithm::GaudiAlgorithm;
     PhotoMultiplierReco(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info())
+      , AlgorithmIDMixin<>(name, info())
     {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputHitCollection", m_outputHitCollection, "");
diff --git a/JugReco/src/components/SimpleClustering.cpp b/JugReco/src/components/SimpleClustering.cpp
index f758dd07..a86635a2 100644
--- a/JugReco/src/components/SimpleClustering.cpp
+++ b/JugReco/src/components/SimpleClustering.cpp
@@ -30,7 +30,7 @@ namespace Jug::Reco {
    *
    * \ingroup reco
    */
-  class SimpleClustering : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+  class SimpleClustering : public GaudiAlgorithm, AlgorithmIDMixin<> {
   public:
     using RecHits  = eic::CalorimeterHitCollection;
     using ProtoClusters = eic::ProtoClusterCollection;
@@ -48,7 +48,7 @@ namespace Jug::Reco {
 
     SimpleClustering(const std::string& name, ISvcLocator* svcLoc) 
       : GaudiAlgorithm(name, svcLoc)
-      , AlgorithmIDMixin(name, info()) {
+      , AlgorithmIDMixin<>(name, info()) {
       declareProperty("inputHitCollection", m_inputHitCollection, "");
       declareProperty("outputProtoClusterCollection", m_outputClusters, "Output proto clusters");
       declareProperty("outputClusterCollection", m_outputClusters, "Output clusters");
diff --git a/JugReco/src/components/TrackerHitReconstruction.cpp b/JugReco/src/components/TrackerHitReconstruction.cpp
index 739c4ad2..d497b491 100644
--- a/JugReco/src/components/TrackerHitReconstruction.cpp
+++ b/JugReco/src/components/TrackerHitReconstruction.cpp
@@ -31,7 +31,7 @@ namespace Jug {
      *
      * \ingroup reco
      */
-    class TrackerHitReconstruction : public GaudiAlgorithm, AlgorithmIDMixin<int32_t> {
+    class TrackerHitReconstruction : public GaudiAlgorithm, AlgorithmIDMixin<> {
     public:
       Gaudi::Property<double>                  m_timeResolution{this, "timeResolution", 10};
       Rndm::Numbers                            m_gaussDist;
@@ -46,7 +46,7 @@ namespace Jug {
       //  ill-formed: using GaudiAlgorithm::GaudiAlgorithm;
       TrackerHitReconstruction(const std::string& name, ISvcLocator* svcLoc)
           : GaudiAlgorithm(name, svcLoc)
-          , AlgorithmIDMixin(name, info())
+          , AlgorithmIDMixin<>(name, info())
       {
         declareProperty("inputHitCollection", m_inputHitCollection, "");
         declareProperty("outputHitCollection", m_outputHitCollection, "");
-- 
GitLab