Repository: eic/EICrecon
Pull request #1763: Low-Q2 Charge Sharing
Author: login: simonge
Labels: name: topic: PID
description: Relates to PID reconstruction
name: topic: infrastructure
name: topic: barrel
name: topic: digitization
Created: 2025-03-21T10:01:24Z | Merged: 2025-05-17T08:00:04Z

### Briefly, what does this PR introduce?
Introduces sensor charge sharing for hits in the Low-Q2 Tagger dividing a single SimTrackerHit into several.

The method originally introduced for LGADs has been simplified and optimized to make it more accessible for future users, making more use of functions provided by dd4hep.

### What kind of change does this PR introduce?
- [ ] Bug fix (issue #__)
- [x] New feature (issue #__)
- [ ] Documentation update
- [ ] Other: __

### Please check if this PR fulfills the following:
- [ ] Tests for the changes have been added
- [ ] Documentation has been added / updated
- [ ] Changes have been communicated to collaborators

### Does this PR introduce breaking changes? What changes might users need to make to their code?
No
### Does this PR change default behavior?
Adds the TaggerTrackerSharedHits to the output and uses them as the input to the digitization.


Diff:
diff --git a/src/algorithms/digi/CMakeLists.txt b/src/algorithms/digi/CMakeLists.txt
index bdf412c252..93cf682f14 100644
--- a/src/algorithms/digi/CMakeLists.txt
+++ b/src/algorithms/digi/CMakeLists.txt
@@ -1,5 +1,3 @@
-cmake_minimum_required(VERSION 3.16)
-
 set(PLUGIN_NAME "algorithms_digi")
 
 # Function creates ${PLUGIN_NAME}_plugin and ${PLUGIN_NAME}_library targets
@@ -12,7 +10,6 @@ plugin_add(${PLUGIN_NAME} WITH_SHARED_LIBRARY WITHOUT_PLUGIN)
 plugin_glob_all(${PLUGIN_NAME})
 
 # Find dependencies
-plugin_add_algorithms(${PLUGIN_NAME})
 plugin_add_dd4hep(${PLUGIN_NAME})
 plugin_add_cern_root(${PLUGIN_NAME})
 plugin_add_event_model(${PLUGIN_NAME})
diff --git a/src/algorithms/digi/LGADChargeSharing.cc b/src/algorithms/digi/LGADChargeSharing.cc
deleted file mode 100644
index 6a491c1b8a..0000000000
--- a/src/algorithms/digi/LGADChargeSharing.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright (C) 2024 Chun Yuen Tsang, Prithwish Tribedy
-//
-// Spread energy deposition from one strip to neighboring strips within sensor boundaries
-
-#include <DD4hep/Alignments.h>
-#include <DD4hep/DetElement.h>
-#include <DD4hep/Readout.h>
-#include <DD4hep/Segmentations.h>
-#include <Evaluator/DD4hepUnits.h>
-#include <Math/GenVector/Cartesian3D.h>
-#include <Math/GenVector/DisplacementVector3D.h>
-#include <TGeoMatrix.h>
-#include <algorithms/geo.h>
-#include <algorithms/service.h>
-#include <edm4hep/Vector3d.h>
-#include <edm4hep/Vector3f.h>
-#include <fmt/core.h>
-#include <algorithm>
-#include <cmath>
-#include <gsl/pointers>
-#include <stdexcept>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "DD4hep/Detector.h"
-#include "LGADChargeSharing.h"
-#include "algorithms/digi/LGADChargeSharingConfig.h"
-#include "services/evaluator/EvaluatorSvc.h"
-
-namespace eicrecon {
-
-void LGADChargeSharing::init() {
-  m_detector  = algorithms::GeoSvc::instance().detector();
-  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
-
-  auto seg  = m_detector->readout(m_cfg.readout).segmentation();
-  auto type = seg.type();
-  // retrieve meaning of cellID bits
-  m_decoder = seg.decoder();
-  m_idSpec  = m_detector->readout(m_cfg.readout).idSpec();
-
-  // convert cellID to name value pairs for EvaluatorSvc to determine of different cells are neighbors
-  std::function hit_pair_to_map = [this](const dd4hep::rec::CellID& id1,
-                                         const dd4hep::rec::CellID& id2) {
-    std::unordered_map<std::string, double> params;
-    for (const auto& p : m_idSpec.fields()) {
-      const std::string& name                  = p.first;
-      const dd4hep::IDDescriptor::Field* field = p.second;
-      params.emplace(name + "_1", field->value(id1));
-      params.emplace(name + "_2", field->value(id2));
-      trace("{}_1 = {}", name, field->value(id1));
-      trace("{}_2 = {}", name, field->value(id2));
-    }
-    return params;
-  };
-
-  auto& serviceSvc = algorithms::ServiceSvc::instance();
-  _is_same_sensor  = serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")
-                        ->compile(m_cfg.same_sensor_condition, hit_pair_to_map);
-}
-
-void LGADChargeSharing::process(const LGADChargeSharing::Input& input,
-                                const LGADChargeSharing::Output& output) const {
-  const auto [simhits] = input;
-  auto [sharedHits]    = output;
-
-  for (const auto& hit : *simhits) {
-    auto cellID         = hit.getCellID();
-    const auto* context = m_converter->findContext(cellID);
-
-    std::unordered_set<dd4hep::rec::CellID> dp;
-    std::vector<dd4hep::rec::CellID> neighbors;
-    this->_findAllNeighborsInSensor(cellID, neighbors, dp);
-
-    double edep       = hit.getEDep();
-    double time       = hit.getTime();
-    auto momentum     = hit.getMomentum();
-    auto truePos      = hit.getPosition();
-    auto localPos_hit = this->_global2Local(
-        context,
-        dd4hep::Position(truePos.x * dd4hep::mm, truePos.y * dd4hep::mm, truePos.z * dd4hep::mm));
-
-    for (const auto neighbor : neighbors) {
-      // integrate over neighbor area to get total energy deposition
-      auto localPos_neighbor = this->_cell2LocalPosition(neighbor);
-      auto cellDimension     = m_converter->cellDimensions(neighbor);
-
-      double edep_cell = edep *
-                         _integralGaus(localPos_hit.x(), m_cfg.sigma_sharingx,
-                                       localPos_neighbor.x() - 0.5 * cellDimension[0],
-                                       localPos_neighbor.x() + 0.5 * cellDimension[0]) *
-                         _integralGaus(localPos_hit.y(), m_cfg.sigma_sharingy,
-                                       localPos_neighbor.y() - 0.5 * cellDimension[1],
-                                       localPos_neighbor.y() + 0.5 * cellDimension[1]);
-
-      if (edep_cell > 0) {
-        auto globalPos = m_converter->position(neighbor);
-        auto hit       = sharedHits->create();
-        hit.setCellID(neighbor);
-        hit.setEDep(edep_cell);
-        hit.setTime(time);
-        hit.setPosition({globalPos.x(), globalPos.y(), globalPos.z()});
-        hit.setMomentum({momentum.x, momentum.y, momentum.z});
-      }
-    }
-  }
-} // LGADChargeSharing:process
-
-void LGADChargeSharing::_findAllNeighborsInSensor(
-    dd4hep::rec::CellID hitCell, std::vector<dd4hep::rec::CellID>& answer,
-    std::unordered_set<dd4hep::rec::CellID>& dp) const {
-  // search all neighbors with DFS
-  answer.push_back(hitCell);
-  dp.insert(hitCell);
-
-  for (const auto& field : m_cfg.neighbor_fields) {
-    // searchDir should either be +1 or -1
-    for (int searchDir = -1; searchDir <= 1; searchDir += 2) {
-      auto fieldID  = m_decoder->get(hitCell, field);
-      auto testCell = hitCell;
-      try {
-        m_decoder->set(testCell, field, fieldID + searchDir);
-      } catch (const std::runtime_error& err) {
-        // catch overflow error
-        // ignore if invalid position ID
-        continue;
-      }
-
-      // check if new cellID really exists
-      try {
-        auto pos = m_converter->position(testCell);
-        if (testCell != m_converter->cellID(pos))
-          continue;
-      } catch (const std::runtime_error& err) {
-        // Ignore CellID that is invalid
-        continue;
-      }
-
-      // only look for cells that have not been searched
-      if (dp.find(testCell) == dp.end()) {
-        if (_is_same_sensor(hitCell, testCell)) {
-          // inside the same sensor
-          this->_findAllNeighborsInSensor(testCell, answer, dp);
-        }
-      }
-    }
-  }
-}
-
-double LGADChargeSharing::_integralGaus(double mean, double sd, double low_lim,
-                                        double up_lim) const {
-  // return integral Gauss(mean, sd) dx from x = low_lim to x = up_lim
-  // default value is set when sd = 0
-  double up  = mean > up_lim ? -0.5 : 0.5;
-  double low = mean > low_lim ? -0.5 : 0.5;
-  if (sd > 0) {
-    up  = -0.5 * std::erf(std::sqrt(2) * (mean - up_lim) / sd);
-    low = -0.5 * std::erf(std::sqrt(2) * (mean - low_lim) / sd);
-  }
-  return up - low;
-}
-
-dd4hep::Position LGADChargeSharing::_cell2LocalPosition(const dd4hep::rec::CellID& cell) const {
-  auto context  = m_converter->findContext(cell); // volume context
-  auto position = m_converter->position(cell);    // global position
-  return this->_global2Local(context, position);
-}
-
-dd4hep::Position LGADChargeSharing::_global2Local(const dd4hep::VolumeManagerContext* context,
-                                                  const dd4hep::Position& pos) const {
-  auto nodeMatrix = context->element.nominal().worldTransformation();
-
-  double g[3], l[3];
-  pos.GetCoordinates(g);
-  nodeMatrix.MasterToLocal(g, l);
-  dd4hep::Position position;
-  position.SetCoordinates(l);
-  return position;
-}
-
-} // namespace eicrecon
diff --git a/src/algorithms/digi/LGADChargeSharing.h b/src/algorithms/digi/LGADChargeSharing.h
deleted file mode 100644
index d79762ad28..0000000000
--- a/src/algorithms/digi/LGADChargeSharing.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright (C) 2024 Chun Yuen Tsang, Prithwish Tribedy
-//
-// Spread energy deposition from one strip to neighboring strips within sensor boundaries
-
-#pragma once
-
-#include <DD4hep/IDDescriptor.h>
-#include <DD4hep/Objects.h>
-#include <DD4hep/VolumeManager.h>
-#include <DDRec/CellIDPositionConverter.h>
-#include <DDSegmentation/BitFieldCoder.h>
-#include <algorithms/algorithm.h>
-#include <edm4hep/SimTrackerHitCollection.h>
-#include <functional>
-#include <string>
-#include <string_view>
-#include <unordered_set>
-#include <vector>
-
-#include "DD4hep/Detector.h"
-#include "algorithms/digi/LGADChargeSharingConfig.h"
-#include "algorithms/interfaces/WithPodConfig.h"
-
-namespace eicrecon {
-
-using LGADChargeSharingAlgorithm =
-    algorithms::Algorithm<algorithms::Input<edm4hep::SimTrackerHitCollection>,
-                          algorithms::Output<edm4hep::SimTrackerHitCollection>>;
-
-class LGADChargeSharing : public LGADChargeSharingAlgorithm,
-                          public WithPodConfig<LGADChargeSharingConfig> {
-
-public:
-  LGADChargeSharing(std::string_view name)
-      : LGADChargeSharingAlgorithm{name, {"TOFBarrelHits"}, {"TOFBarrelSharedHits"}, ""} {};
-
-  void init() final;
-  void process(const Input&, const Output&) const final;
-
-private:
-  void _findAllNeighborsInSensor(dd4hep::rec::CellID hitCell,
-                                 std::vector<dd4hep::rec::CellID>& answer,
-                                 std::unordered_set<dd4hep::rec::CellID>& dp) const;
-  double _integralGaus(double mean, double sd, double low_lim, double up_lim) const;
-  dd4hep::Position _cell2LocalPosition(const dd4hep::rec::CellID& cell) const;
-  dd4hep::Position _global2Local(const dd4hep::VolumeManagerContext* context,
-                                 const dd4hep::Position& pos) const;
-
-  const dd4hep::DDSegmentation::BitFieldCoder* m_decoder  = nullptr;
-  const dd4hep::Detector* m_detector                      = nullptr;
-  const dd4hep::rec::CellIDPositionConverter* m_converter = nullptr;
-  dd4hep::IDDescriptor m_idSpec;
-
-  // helper function to find neighbors
-  std::function<bool(const dd4hep::rec::CellID& id1, const dd4hep::rec::CellID& id2)>
-      _is_same_sensor;
-};
-
-} // namespace eicrecon
diff --git a/src/algorithms/digi/LGADChargeSharingConfig.h b/src/algorithms/digi/LGADChargeSharingConfig.h
deleted file mode 100644
index 9374c5bce9..0000000000
--- a/src/algorithms/digi/LGADChargeSharingConfig.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright (C) 2024 Souvik Paul
-
-#pragma once
-
-#include <DD4hep/DD4hepUnits.h>
-
-namespace eicrecon {
-
-struct LGADChargeSharingConfig {
-  // Parameters of AC-LGAD signal generation
-  double sigma_sharingx;
-  double sigma_sharingy;
-
-  std::string readout;
-  std::string same_sensor_condition;
-  std::vector<std::string> neighbor_fields;
-};
-
-} // namespace eicrecon
diff --git a/src/algorithms/digi/SiliconChargeSharing.cc b/src/algorithms/digi/SiliconChargeSharing.cc
new file mode 100644
index 0000000000..3cd4fe62f9
--- /dev/null
+++ b/src/algorithms/digi/SiliconChargeSharing.cc
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    const auto* element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    auto [transformIt, transformInserted] =
+        m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    auto [segmentationIt, segmentationInserted] =
+        m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid requiring segmentation
+    // to be a CartesianGridXY, throwing exception in getLocalSegmentation
+    try {
+      dd4hep::Box box = element->solid();
+      m_xy_range_map.try_emplace(element, box->GetDX(), box->GetDY());
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     transformIt->second);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::unordered_map<dd4hep::rec::CellID, float> cell_charge;
+
+    // Warning: This function is recursive, it stops shen it finds the edge of a detector element
+    // or when the energy deposited in a cell is below the configured threshold
+    findAllNeighborsInSensor(cellID, tested_cells, edep, hitPos, segmentationIt->second,
+                             m_xy_range_map[element], hit, sharedHits);
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    const float edep, const dd4hep::Position hitPos,
+    const dd4hep::DDSegmentation::CartesianGridXY* segmentation,
+    const std::pair<double, double>& xy_range, const edm4hep::SimTrackerHit& hit,
+    edm4hep::SimTrackerHitCollection* sharedHits) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > xy_range.first || std::abs(localPos.y()) > xy_range.second) {
+    return;
+  }
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell <= m_cfg.min_edep) {
+    return;
+  }
+
+  // Create a new simhit for cell with deposited energy
+  auto globalCellPos = m_converter->position(testCellID);
+
+  edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+  shared_hit.setCellID(testCellID);
+  shared_hit.setEDep(edepCell);
+  shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                          globalCellPos.z() / dd4hep::mm});
+  sharedHits->push_back(shared_hit);
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, edep, hitPos, segmentation, xy_range,
+                               hit, sharedHits);
+    }
+  }
+}
+
+// Calculate integral of Gaussian distribution
+float SiliconChargeSharing::integralGaus(float mean, float sd, float low_lim, float up_lim) const {
+  // return integral Gauss(mean, sd) dx from x = low_lim to x = up_lim
+  // default value is set when sd = 0
+  float up  = mean > up_lim ? -0.5 : 0.5;
+  float low = mean > low_lim ? -0.5 : 0.5;
+  if (sd > 0) {
+    up  = -0.5 * std::erf(std::sqrt(2) * (mean - up_lim) / sd);
+    low = -0.5 * std::erf(std::sqrt(2) * (mean - low_lim) / sd);
+  }
+  return up - low;
+}
+
+// Convert cellID to local position
+dd4hep::Position SiliconChargeSharing::cell2LocalPosition(const dd4hep::rec::CellID& cell) const {
+  auto position = m_seg->position(cell); // local position
+  return position;
+}
+
+// Convert global position to local position
+dd4hep::Position SiliconChargeSharing::global2Local(const dd4hep::Position& globalPosition,
+                                                    const TGeoHMatrix* transform) const {
+
+  double g[3];
+  double l[3];
+
+  globalPosition.GetCoordinates(g);
+  transform->MasterToLocal(g, l);
+  dd4hep::Position localPosition;
+  localPosition.SetCoordinates(l);
+  return localPosition;
+}
+
+// Calculate energy deposition in a cell relative to the hit position
+float SiliconChargeSharing::energyAtCell(const double xDimension, const double yDimension,
+                                         const dd4hep::Position localPos,
+                                         const dd4hep::Position hitPos, const float edep) const {
+  float energy = edep *
+                 integralGaus(hitPos.x(), m_cfg.sigma_sharingx, localPos.x() - 0.5 * xDimension,
+                              localPos.x() + 0.5 * xDimension) *
+                 integralGaus(hitPos.y(), m_cfg.sigma_sharingy, localPos.y() - 0.5 * yDimension,
+                              localPos.y() + 0.5 * yDimension);
+  return energy;
+}
+
+// Get the segmentation relevant to a cellID
+const dd4hep::DDSegmentation::CartesianGridXY*
+SiliconChargeSharing::getLocalSegmentation(const dd4hep::rec::CellID& cellID) const {
+  // Get the segmentation type
+  auto segmentation_type                                   = m_seg.type();
+  const dd4hep::DDSegmentation::Segmentation* segmentation = m_seg.segmentation();
+  // Check if the segmentation is a multi-segmentation
+  while (segmentation_type == "MultiSegmentation") {
+    const auto* multi_segmentation =
+        dynamic_cast<const dd4hep::DDSegmentation::MultiSegmentation*>(segmentation);
+    segmentation      = &multi_segmentation->subsegmentation(cellID);
+    segmentation_type = segmentation->type();
+  }
+
+  // Try to cast the segmentation to CartesianGridXY
+  const auto* cartesianGrid =
+      dynamic_cast<const dd4hep::DDSegmentation::CartesianGridXY*>(segmentation);
+  if (!cartesianGrid) {
+    throw std::runtime_error("Segmentation is not of type CartesianGridXY");
+  }
+
+  return cartesianGrid;
+}
+
+} // namespace eicrecon
diff --git a/src/algorithms/digi/SiliconChargeSharing.h b/src/algorithms/digi/SiliconChargeSharing.h
new file mode 100644
index 0000000000..d656cb464f
--- /dev/null
+++ b/src/algorithms/digi/SiliconChargeSharing.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#pragma once
+
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Segmentations.h>
+#include <DDRec/CellIDPositionConverter.h>
+#include <DDSegmentation/CartesianGridXY.h>
+#include <TGeoMatrix.h>
+#include <algorithms/algorithm.h>
+#include <edm4hep/SimTrackerHitCollection.h>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+#include "algorithms/interfaces/WithPodConfig.h"
+
+namespace eicrecon {
+
+using SiliconChargeSharingAlgorithm =
+    algorithms::Algorithm<algorithms::Input<edm4hep::SimTrackerHitCollection>,
+                          algorithms::Output<edm4hep::SimTrackerHitCollection>>;
+
+class SiliconChargeSharing : public SiliconChargeSharingAlgorithm,
+                             public WithPodConfig<SiliconChargeSharingConfig> {
+
+public:
+  SiliconChargeSharing(std::string_view name)
+      : SiliconChargeSharingAlgorithm{name, {"inputHits"}, {"outputSharedHits"}, ""} {};
+
+  void init() final;
+  void process(const Input&, const Output&) const final;
+
+private:
+  void findAllNeighborsInSensor(const dd4hep::rec::CellID test_CellID,
+                                std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+                                const float edep, const dd4hep::Position hitPos,
+                                const dd4hep::DDSegmentation::CartesianGridXY* segmentation,
+                                const std::pair<double, double>& xy_range,
+                                const edm4hep::SimTrackerHit& hit,
+                                edm4hep::SimTrackerHitCollection* sharedHits) const;
+  float energyAtCell(const double xDimension, const double yDimension,
+                     const dd4hep::Position localPos, const dd4hep::Position hitPos,
+                     const float edep) const;
+  float integralGaus(float mean, float sd, float low_lim, float up_lim) const;
+  dd4hep::Position cell2LocalPosition(const dd4hep::rec::CellID& cell) const;
+  dd4hep::Position global2Local(const dd4hep::Position& globalPosition,
+                                const TGeoHMatrix* transform) const;
+  const dd4hep::DDSegmentation::CartesianGridXY*
+  getLocalSegmentation(const dd4hep::rec::CellID& cellID) const;
+
+  mutable std::unordered_map<const dd4hep::DetElement*, const TGeoHMatrix*> m_transform_map;
+  mutable std::unordered_map<const dd4hep::DetElement*,
+                             const dd4hep::DDSegmentation::CartesianGridXY*>
+      m_segmentation_map;
+  mutable std::unordered_map<const dd4hep::DetElement*, const std::pair<double, double>>
+      m_xy_range_map;
+  const dd4hep::rec::CellIDPositionConverter* m_converter = nullptr;
+  dd4hep::Segmentation m_seg;
+};
+
+} // namespace eicrecon
diff --git a/src/algorithms/digi/SiliconChargeSharingConfig.h b/src/algorithms/digi/SiliconChargeSharingConfig.h
new file mode 100644
index 0000000000..05c69fae14
--- /dev/null
+++ b/src/algorithms/digi/SiliconChargeSharingConfig.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024 Souvik Paul
+
+#pragma once
+
+namespace eicrecon {
+
+struct SiliconChargeSharingConfig {
+  // Parameters of Silicon signal generation
+  float sigma_sharingx;
+  float sigma_sharingy;
+  float min_edep;
+  std::string readout;
+};
+
+} // namespace eicrecon
diff --git a/src/algorithms/digi/SiliconPulseGeneration.cc b/src/algorithms/digi/SiliconPulseGeneration.cc
index 641215efee..051b286c02 100644
--- a/src/algorithms/digi/SiliconPulseGeneration.cc
+++ b/src/algorithms/digi/SiliconPulseGeneration.cc
@@ -153,15 +153,12 @@ void SiliconPulseGeneration::process(const SiliconPulseGeneration::Input& input,
     // Calculate nearest timestep to the hit time rounded down (assume clocks aligned with time 0)
     double signal_time = m_cfg.timestep * std::floor(time / m_cfg.timestep);
 
-    auto time_series = rawPulses->create();
-    time_series.setCellID(cellID);
-    time_series.setInterval(m_cfg.timestep);
-
     bool passed_threshold = false;
-    int skip_bins         = 0;
+    uint32_t skip_bins    = 0;
     float integral        = 0;
+    std::vector<float> pulse;
 
-    for (int i = 0; i < m_cfg.max_time_bins; i++) {
+    for (uint32_t i = 0; i < m_cfg.max_time_bins; i++) {
       double t    = signal_time + i * m_cfg.timestep - time;
       auto signal = (*m_pulse)(t, charge);
       if (std::abs(signal) < m_cfg.ignore_thres) {
@@ -174,12 +171,23 @@ void SiliconPulseGeneration::process(const SiliconPulseGeneration::Input& input,
         }
       }
       passed_threshold = true;
-      time_series.addToAmplitude(signal);
+      pulse.push_back(signal);
       integral += signal;
     }
 
+    if (!passed_threshold) {
+      continue;
+    }
+
+    auto time_series = rawPulses->create();
+    time_series.setCellID(cellID);
+    time_series.setInterval(m_cfg.timestep);
     time_series.setTime(signal_time + skip_bins * m_cfg.timestep);
 
+    for (const auto& value : pulse) {
+      time_series.addToAmplitude(value);
+    }
+
 #if EDM4EIC_VERSION_MAJOR > 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR >= 1)
     time_series.setIntegral(integral);
     time_series.setPosition(
diff --git a/src/algorithms/digi/SiliconPulseGenerationConfig.h b/src/algorithms/digi/SiliconPulseGenerationConfig.h
index ed8e351a5b..6e4614cf15 100644
--- a/src/algorithms/digi/SiliconPulseGenerationConfig.h
+++ b/src/algorithms/digi/SiliconPulseGenerationConfig.h
@@ -14,7 +14,7 @@ struct SiliconPulseGenerationConfig {
   double ignore_thres                    = 10; // When EDep drops below this value pulse stops
   double timestep          = 0.2 * edm4eic::unit::ns; // Minimum digitization time step
   double min_sampling_time = 0 * edm4eic::unit::ns;   // Minimum sampling time
-  int max_time_bins        = 10000;
+  uint32_t max_time_bins   = 10000;
 };
 
 } // namespace eicrecon
diff --git a/src/detectors/BTOF/BTOF.cc b/src/detectors/BTOF/BTOF.cc
index 4cc7f07b12..863ba7ae3b 100644
--- a/src/detectors/BTOF/BTOF.cc
+++ b/src/detectors/BTOF/BTOF.cc
@@ -19,11 +19,11 @@
 #include "algorithms/pid_lut/PIDLookupConfig.h"
 #include "extensions/jana/JOmniFactoryGeneratorT.h"
 #include "factories/digi/EICROCDigitization_factory.h"
-#include "factories/digi/LGADChargeSharing_factory.h"
 #include "factories/digi/SiliconPulseDiscretization_factory.h"
 #include "factories/digi/SiliconPulseGeneration_factory.h"
 #include "factories/digi/SiliconTrackerDigi_factory.h"
 #include "factories/tracking/TrackerHitReconstruction_factory.h"
+#include "factories/digi/SiliconChargeSharing_factory.h"
 #include "factories/digi/PulseCombiner_factory.h"
 #include "global/pid_lut/PIDLookup_factory.h"
 #include "services/geometry/dd4hep/DD4hep_service.h"
@@ -52,13 +52,14 @@ void InitPlugin(JApplication* app) {
       },
       app)); // Hit reco default config for factories
 
-  app->Add(new JOmniFactoryGeneratorT<LGADChargeSharing_factory>(
-      "LGADChargeSharing", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
-      {.sigma_sharingx        = 0.1 * dd4hep::cm,
-       .sigma_sharingy        = 0.5 * dd4hep::cm,
-       .readout               = "TOFBarrelHits",
-       .same_sensor_condition = "sensor_1 == sensor_2",
-       .neighbor_fields       = {"x", "y"}},
+  app->Add(new JOmniFactoryGeneratorT<SiliconChargeSharing_factory>(
+      "TOFBarrelSharedHits", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
+      {
+          .sigma_sharingx = 0.1 * dd4hep::cm,
+          .sigma_sharingy = 0.5 * dd4hep::cm,
+          .min_edep       = 0.0 * edm4eic::unit::GeV,
+          .readout        = "TOFBarrelHits",
+      },
       app));
 
   // calculation of the extreme values for Landau distribution can be found on lin 514-520 of
diff --git a/src/detectors/LOWQ2/LOWQ2.cc b/src/detectors/LOWQ2/LOWQ2.cc
index 27ad43d151..edf984a375 100644
--- a/src/detectors/LOWQ2/LOWQ2.cc
+++ b/src/detectors/LOWQ2/LOWQ2.cc
@@ -3,6 +3,7 @@
 //
 //
 
+#include <Evaluator/DD4hepUnits.h>
 #include <JANA/JApplication.h>
 #include <edm4eic/EDM4eicVersion.h>
 #include <edm4eic/MCRecoTrackParticleAssociation.h>
@@ -22,6 +23,7 @@
 #include "extensions/jana/JOmniFactoryGeneratorT.h"
 #include "factories/digi/PulseCombiner_factory.h"
 #include "factories/digi/PulseNoise_factory.h"
+#include "factories/digi/SiliconChargeSharing_factory.h"
 #include "factories/digi/SiliconPulseGeneration_factory.h"
 #include "factories/digi/SiliconTrackerDigi_factory.h"
 #include "factories/fardetectors/FarDetectorLinearProjection_factory.h"
@@ -45,9 +47,20 @@ void InitPlugin(JApplication* app) {
 
   using namespace eicrecon;
 
+  std::string readout = "TaggerTrackerHits";
+
+  app->Add(new JOmniFactoryGeneratorT<SiliconChargeSharing_factory>(
+      "TaggerTrackerChargeSharing", {"TaggerTrackerHits"}, {"TaggerTrackerSharedHits"},
+      {
+          .sigma_sharingx = 15 * dd4hep::um,
+          .sigma_sharingy = 15 * dd4hep::um,
+          .min_edep       = 0.1 * edm4eic::unit::keV,
+          .readout        = readout,
+      },
+      app));
   //  Generate signal pulse from hits
   app->Add(new JOmniFactoryGeneratorT<SiliconPulseGeneration_factory>(
-      "TaggerTrackerPulseGeneration", {"TaggerTrackerHits"}, {"TaggerTrackerHitPulses"},
+      "TaggerTrackerPulseGeneration", {"TaggerTrackerSharedHits"}, {"TaggerTrackerHitPulses"},
       {
           .pulse_shape_function = "LandauPulse",
           .pulse_shape_params   = {1.0, 2 * edm4eic::unit::ns},
@@ -96,7 +109,6 @@ void InitPlugin(JApplication* app) {
 
   // Divide collection based on geometry segmentation labels
   // This should really be done before digitization as summing hits in the same cell couldn't even be mixed between layers. At the moment just prep for clustering.
-  std::string readout = "TaggerTrackerHits";
   std::vector<std::string> geometryLabels{"module", "layer"};
   std::vector<int> moduleIDs{1, 2};
   std::vector<int> layerIDs{0, 1, 2, 3};
diff --git a/src/factories/digi/PulseNoise_factory.h b/src/factories/digi/PulseNoise_factory.h
index 5bdd8e724e..5a285775e5 100644
--- a/src/factories/digi/PulseNoise_factory.h
+++ b/src/factories/digi/PulseNoise_factory.h
@@ -3,9 +3,8 @@
 
 #pragma once
 
-#include <iostream>
-
 #include "algorithms/digi/PulseNoise.h"
+#include "services/algorithms_init/AlgorithmsInit_service.h"
 #include "extensions/jana/JOmniFactory.h"
 
 namespace eicrecon {
diff --git a/src/factories/digi/LGADChargeSharing_factory.h b/src/factories/digi/SiliconChargeSharing_factory.h
similarity index 51%
rename from src/factories/digi/LGADChargeSharing_factory.h
rename to src/factories/digi/SiliconChargeSharing_factory.h
index 4706c0bdf8..567e8c1b7d 100644
--- a/src/factories/digi/LGADChargeSharing_factory.h
+++ b/src/factories/digi/SiliconChargeSharing_factory.h
@@ -1,35 +1,35 @@
 // SPDX-License-Identifier: LGPL-3.0-or-later
-// Copyright (C) 2024 Chun Yuen Tsang
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Simon Gardner
 
 #pragma once
 
+#include "algorithms/digi/SiliconChargeSharing.h"
+#include "services/algorithms_init/AlgorithmsInit_service.h"
 #include "extensions/jana/JOmniFactory.h"
 
-#include "algorithms/digi/LGADChargeSharing.h"
-#include <iostream>
-
 namespace eicrecon {
 
-class LGADChargeSharing_factory
-    : public JOmniFactory<LGADChargeSharing_factory, LGADChargeSharingConfig> {
+class SiliconChargeSharing_factory
+    : public JOmniFactory<SiliconChargeSharing_factory, SiliconChargeSharingConfig> {
 public:
-  using AlgoT = eicrecon::LGADChargeSharing;
+  using AlgoT = eicrecon::SiliconChargeSharing;
 
 private:
   std::unique_ptr<AlgoT> m_algo;
 
   PodioInput<edm4hep::SimTrackerHit> m_in_sim_track{this};
-
   PodioOutput<edm4hep::SimTrackerHit> m_out_reco_particles{this};
 
-  ParameterRef<double> m_sigma_sharingx{this, "sigmaSharingX", config().sigma_sharingx};
-  ParameterRef<double> m_sigma_sharingy{this, "sigmaSharingY", config().sigma_sharingy};
-
   Service<AlgorithmsInit_service> m_algorithmsInit{this};
 
+  ParameterRef<float> m_sigma_sharingx{this, "sigmaSharingX", config().sigma_sharingx};
+  ParameterRef<float> m_sigma_sharingy{this, "sigmaSharingY", config().sigma_sharingy};
+  ParameterRef<float> m_min_edep{this, "minEDep", config().min_edep};
+  ParameterRef<std::string> m_readout{this, "readout", config().readout};
+
 public:
   void Configure() {
-    m_algo = std::make_unique<eicrecon::LGADChargeSharing>(GetPrefix());
+    m_algo = std::make_unique<AlgoT>(GetPrefix());
     m_algo->level(static_cast<algorithms::LogLevel>(logger()->level()));
     m_algo->applyConfig(config());
     m_algo->init();
diff --git a/src/factories/digi/SiliconPulseDiscretization_factory.h b/src/factories/digi/SiliconPulseDiscretization_factory.h
index ba2052f869..3133f36fc8 100644
--- a/src/factories/digi/SiliconPulseDiscretization_factory.h
+++ b/src/factories/digi/SiliconPulseDiscretization_factory.h
@@ -3,10 +3,9 @@
 
 #pragma once
 
-#include "extensions/jana/JOmniFactory.h"
-
+#include "services/algorithms_init/AlgorithmsInit_service.h"
 #include "algorithms/digi/SiliconPulseDiscretization.h"
-#include <iostream>
+#include "extensions/jana/JOmniFactory.h"
 
 namespace eicrecon {
 
@@ -33,7 +32,7 @@ class SiliconPulseDiscretization_factory
 
 public:
   void Configure() {
-    m_algo = std::make_unique<eicrecon::SiliconPulseDiscretization>(GetPrefix());
+    m_algo = std::make_unique<AlgoT>(GetPrefix());
     m_algo->level(static_cast<algorithms::LogLevel>(logger()->level()));
     m_algo->applyConfig(config());
     m_algo->init();
diff --git a/src/factories/digi/SiliconPulseGeneration_factory.h b/src/factories/digi/SiliconPulseGeneration_factory.h
index b6fc153bd4..83fa69813e 100644
--- a/src/factories/digi/SiliconPulseGeneration_factory.h
+++ b/src/factories/digi/SiliconPulseGeneration_factory.h
@@ -4,10 +4,9 @@
 
 #pragma once
 
-#include "extensions/jana/JOmniFactory.h"
-
 #include "algorithms/digi/SiliconPulseGeneration.h"
-#include <iostream>
+#include "services/algorithms_init/AlgorithmsInit_service.h"
+#include "extensions/jana/JOmniFactory.h"
 
 namespace eicrecon {
 
@@ -33,13 +32,13 @@ class SiliconPulseGeneration_factory
   ParameterRef<double> m_timestep{this, "timestep", config().timestep};
   ParameterRef<double> m_ignore_thres{this, "ignoreThreshold", config().ignore_thres};
   ParameterRef<double> m_min_sampling_time{this, "minSamplingTime", config().min_sampling_time};
-  ParameterRef<int> m_max_time_bins{this, "maxTimeBins", config().max_time_bins};
+  ParameterRef<uint32_t> m_max_time_bins{this, "maxTimeBins", config().max_time_bins};
 
   Service<AlgorithmsInit_service> m_algorithmsInit{this};
 
 public:
   void Configure() {
-    m_algo = std::make_unique<eicrecon::SiliconPulseGeneration>(GetPrefix());
+    m_algo = std::make_unique<AlgoT>(GetPrefix());
     m_algo->level(static_cast<algorithms::LogLevel>(logger()->level()));
     m_algo->applyConfig(config());
     m_algo->init();
diff --git a/src/services/io/podio/JEventProcessorPODIO.cc b/src/services/io/podio/JEventProcessorPODIO.cc
index 9a56b0fded..782bacb49c 100644
--- a/src/services/io/podio/JEventProcessorPODIO.cc
+++ b/src/services/io/podio/JEventProcessorPODIO.cc
@@ -132,6 +132,7 @@ JEventProcessorPODIO::JEventProcessorPODIO() {
 
     // LOWQ2 hits
     "TaggerTrackerHits",
+    "TaggerTrackerSharedHits",
     "TaggerTrackerHitPulses",
     "TaggerTrackerCombinedPulses",
     "TaggerTrackerCombinedPulsesWithNoise",
diff --git a/src/tests/algorithms_test/digi_SiliconPulseGeneration.cc b/src/tests/algorithms_test/digi_SiliconPulseGeneration.cc
index 09631b655a..cf811c08dc 100644
--- a/src/tests/algorithms_test/digi_SiliconPulseGeneration.cc
+++ b/src/tests/algorithms_test/digi_SiliconPulseGeneration.cc
@@ -34,6 +34,7 @@ TEST_CASE("SiliconPulseGeneration generates correct number of pulses", "[Silicon
   eicrecon::SiliconPulseGenerationConfig cfg;
   cfg.pulse_shape_function = "LandauPulse"; // Example pulse shape
   cfg.pulse_shape_params   = {1.0, 1.0};    // Example parameters for the pulse shape
+  cfg.ignore_thres         = 1;
 
   algo.applyConfig(cfg);
   algo.init();


Reviews:
user: login: veprbl
body: Looks good. You dug out some interesting functionality for DD4hep's segmentations, although, it may not work to find neighbours this way in detectors with pixel volumes.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T02:24:34Z
user: login: ssedd1123
body: I can't comment on the TaggerTracker issue, but I verify that this new charge sharing code works on BTOF sensors. The new class looks good from my side. 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T15:09:49Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T21:07:22Z
user: login: veprbl
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T21:16:45Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T15:49:51Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T15:52:05Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T15:54:41Z
user: login: ssedd1123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T15:55:12Z
user: login: ssedd1123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T15:59:19Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T16:02:44Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T16:04:24Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T16:11:50Z
user: login: simonge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T14:11:32Z
user: login: simonge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T14:16:40Z
user: login: wdconinc
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:23:25Z
user: login: veprbl
body: Diff looks good, and capybara looks reasonable.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-17T04:40:07Z

Review comments:
diff_hunk: @@ -52,13 +52,14 @@ void InitPlugin(JApplication* app) {
       },
       app)); // Hit reco default config for factories
 
-  app->Add(new JOmniFactoryGeneratorT<LGADChargeSharing_factory>(
-      "LGADChargeSharing", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
-      {.sigma_sharingx        = 0.1 * dd4hep::cm,
-       .sigma_sharingy        = 0.5 * dd4hep::cm,
-       .readout               = "TOFBarrelHits",
-       .same_sensor_condition = "sensor_1 == sensor_2",
-       .neighbor_fields       = {"x", "y"}},
+  app->Add(new JOmniFactoryGeneratorT<SiliconChargeSharing_factory>(
+      "TOFBarrelChargeSharing", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
+      {
+          .sigma_sharingx = 0.1 * dd4hep::cm,
+          .sigma_sharingy = 0.5 * dd4hep::cm,
+          .min_edep       = 0.0,
path: src/detectors/BTOF/BTOF.cc
user: login: veprbl
body: ```suggestion
          .min_edep       = 0.0 * edm4eic::unit::GeV,
```
created_at: 2025-05-09T02:05:56Z
author_association: MEMBER
diff_hunk: @@ -45,9 +47,20 @@ void InitPlugin(JApplication* app) {
 
   using namespace eicrecon;
 
+  std::string readout = "TaggerTrackerHits";
+
+  app->Add(new JOmniFactoryGeneratorT<SiliconChargeSharing_factory>(
+      "TaggerTrackerChargeSharing", {"TaggerTrackerHits"}, {"TaggerTrackerSharedHits"},
+      {
+          .sigma_sharingx = 15 * dd4hep::um,
+          .sigma_sharingy = 15 * dd4hep::um,
+          .min_edep       = 1.0e-7,
path: src/detectors/LOWQ2/LOWQ2.cc
user: login: veprbl
body: ```suggestion
          .min_edep       = 0.1 * edm4eic::unit::keV,
```
created_at: 2025-05-09T02:07:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,212 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    //cout number of neighbors found
+    // error("Found {} neighbors for cellID {}", cell_charge.size(), cellID);
+
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: veprbl
body: ```suggestion
```
Please remove or uncomment and use `trace()`.
created_at: 2025-05-09T02:17:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,212 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    //cout number of neighbors found
+    // error("Found {} neighbors for cellID {}", cell_charge.size(), cellID);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit newHit = hit.clone();
+      newHit.setCellID(testCellID);
+      newHit.setEDep(edep_cell);
+      newHit.setPosition({globalCellPos.x(), globalCellPos.y(), globalCellPos.z()});
+      sharedHits->push_back(newHit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    // error("Testing neighbour cellID {}", neighbourCell);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: veprbl
body: ```suggestion
```
Uncomment and use `trace()` or remove.
created_at: 2025-05-09T02:18:24Z
author_association: MEMBER
diff_hunk: @@ -52,13 +52,14 @@ void InitPlugin(JApplication* app) {
       },
       app)); // Hit reco default config for factories
 
-  app->Add(new JOmniFactoryGeneratorT<LGADChargeSharing_factory>(
-      "LGADChargeSharing", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
-      {.sigma_sharingx        = 0.1 * dd4hep::cm,
-       .sigma_sharingy        = 0.5 * dd4hep::cm,
-       .readout               = "TOFBarrelHits",
-       .same_sensor_condition = "sensor_1 == sensor_2",
-       .neighbor_fields       = {"x", "y"}},
+  app->Add(new JOmniFactoryGeneratorT<SiliconChargeSharing_factory>(
+      "TOFBarrelChargeSharing", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
path: src/detectors/BTOF/BTOF.cc
user: login: wdconinc
body: Many factories (for historical reasons) use the primary output collection as the name. Consistency helps when changing factory properties.
```suggestion
      "TOFBarrelSharedHits", {"TOFBarrelHits"}, {"TOFBarrelSharedHits"},
```
created_at: 2025-05-09T21:07:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,68 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#pragma once
+
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Segmentations.h>
+#include <DDRec/CellIDPositionConverter.h>
+#include <DDSegmentation/CartesianGridXY.h>
+#include <TGeoMatrix.h>
+#include <algorithms/algorithm.h>
+#include <edm4hep/SimTrackerHitCollection.h>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+#include "algorithms/interfaces/WithPodConfig.h"
+
+namespace eicrecon {
+
+using SiliconChargeSharingAlgorithm =
+    algorithms::Algorithm<algorithms::Input<edm4hep::SimTrackerHitCollection>,
+                          algorithms::Output<edm4hep::SimTrackerHitCollection>>;
+
+class SiliconChargeSharing : public SiliconChargeSharingAlgorithm,
+                             public WithPodConfig<SiliconChargeSharingConfig> {
+
+public:
+  SiliconChargeSharing(std::string_view name)
+      : SiliconChargeSharingAlgorithm{name, {"TOFBarrelHits"}, {"TOFBarrelSharedHits"}, ""} {};
path: src/algorithms/digi/SiliconChargeSharing.h
user: login: veprbl
body: ```suggestion
      : SiliconChargeSharingAlgorithm{name, {"inputHits"}, {"outputSharedHits"}, ""} {};
```
created_at: 2025-05-09T21:16:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Since this is recursive, can you add a comment on how boundedness is ensured, what worst case performance conditions are, and what typical performance scaling is? We've run into O(n^3) inefficiencies when a different detector adopted a generic algorithm without thinking about the increase in detector segmentation.
created_at: 2025-05-10T15:49:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Note that we spend 20% of the reconstruction time on TOF digitization now... https://github.com/eic/EICrecon/actions/runs/14943453065/job/41984210764?pr=1763#step:7:1271
created_at: 2025-05-10T15:52:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Addressing performance can be a future PR, but the comment about scaling would still be useful before merging this.
created_at: 2025-05-10T15:54:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: ssedd1123
body: This is dynamic programming. Every cell that has been searched will be stored in `tested_cells`, and once a cellID appears in `test_cells`, the recursion on line 135 will not execute. Therefore, the number of times `findAllNeighborsInSensor` is called should equals to number of cells in a sensors (+ boundaries if you want to be precise). `tested_cells` is unordered_map, so searching complexity is ~O(1). Therefore, complexity of findAllNeighborsInSensor should be O(n), where n is the total number of cells in a sensor.

Edit: The complexity should be O(mn), where m is the number of neighbors. Line 133 will be executed m times for every call to `findAllNeighborsInSensor`. 
created_at: 2025-05-10T15:55:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: ssedd1123
body: I am assuming everything in `findAllNeighborsInSensor` has O(1) complexity. I don't actually know how `segmentation->neighbours` scales with number of neighbors. 
created_at: 2025-05-10T15:59:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: I'm assuming neighbors scales with dimensions only, O(d).
created_at: 2025-05-10T16:02:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Maybe the impact of digitization on performance is then mostly due to the time / 1024 subsampling of the pulse shapes. The janatop output isn't always exclusive.
created_at: 2025-05-10T16:04:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Can/should the map be from element to pair {x,y}?
created_at: 2025-05-10T16:10:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: And should there not be a condition that this is CartesianGridXY, since for CartesianGridXZ the ranges would be incorrect?
created_at: 2025-05-10T16:11:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: simonge
body: I have swapped to a {x,y} pair.
I believe the getLocalSegmentation should currently restrict it to XY segmentation, this could be generalized if necessary in the future.
created_at: 2025-05-12T14:11:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: simonge
body: I have just pushed some updates which should improve the performance, primarily not adding hits with 0 deposited energy across the whole detector. I had delayed this so that the BTOF diffs could be checked without the cut as it was before. This was mostly fine for the BTOF as the number of cells isn't huge but larger detectors it would have caused significant issues. 
created_at: 2025-05-12T14:16:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,209 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright (C) 2024-2025 Chun Yuen Tsang, Prithwish Tribedy, Simon Gardner
+//
+// Spread energy deposition from one strip to neighboring strips within sensor boundaries
+
+#include <DD4hep/Alignments.h>
+#include <DD4hep/DetElement.h>
+#include <DD4hep/Handle.h>
+#include <DD4hep/Objects.h>
+#include <DD4hep/Readout.h>
+#include <DD4hep/Segmentations.h>
+#include <DD4hep/Shapes.h>
+#include <DD4hep/VolumeManager.h>
+#include <DD4hep/detail/SegmentationsInterna.h>
+#include <DDSegmentation/MultiSegmentation.h>
+#include <DDSegmentation/Segmentation.h>
+#include <Evaluator/DD4hepUnits.h>
+#include <Math/GenVector/Cartesian3D.h>
+#include <Math/GenVector/DisplacementVector3D.h>
+#include <TGeoBBox.h>
+#include <TGeoMatrix.h>
+#include <algorithms/geo.h>
+#include <edm4hep/Vector3d.h>
+#include <fmt/core.h>
+#include <algorithm>
+#include <cmath>
+#include <gsl/pointers>
+#include <set>
+#include <stdexcept>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+
+#include "DD4hep/Detector.h"
+#include "SiliconChargeSharing.h"
+#include "algorithms/digi/SiliconChargeSharingConfig.h"
+
+namespace eicrecon {
+
+void SiliconChargeSharing::init() {
+  m_converter = algorithms::GeoSvc::instance().cellIDPositionConverter();
+  m_seg       = algorithms::GeoSvc::instance().detector()->readout(m_cfg.readout).segmentation();
+}
+
+void SiliconChargeSharing::process(const SiliconChargeSharing::Input& input,
+                                   const SiliconChargeSharing::Output& output) const {
+  const auto [simhits] = input;
+  auto [sharedHits]    = output;
+
+  for (const auto& hit : *simhits) {
+
+    auto cellID = hit.getCellID();
+
+    auto element = &m_converter->findContext(cellID)->element; // volume context
+    // ToDo: Move this to init() and set it once for every detelement associated with the readout
+    // Set transformation map if it hasn't already been set
+    m_transform_map.try_emplace(element, &element->nominal().worldTransformation());
+    m_segmentation_map.try_emplace(element, getLocalSegmentation(cellID));
+
+    // Try and get a box of the detectorElement solid
+    try {
+      dd4hep::Box box = element->solid();
+
+      if (box) {
+        m_x_range_map.try_emplace(element, box->GetDX());
+        m_y_range_map.try_emplace(element, box->GetDY());
+      }
+    } catch (const std::bad_cast& e) {
+      error("Failed to cast solid to Box: {}", e.what());
+    }
+
+    auto edep         = hit.getEDep();
+    auto globalHitPos = hit.getPosition();
+    auto hitPos =
+        global2Local(dd4hep::Position(globalHitPos.x * dd4hep::mm, globalHitPos.y * dd4hep::mm,
+                                      globalHitPos.z * dd4hep::mm),
+                     m_transform_map[element]);
+
+    std::unordered_set<dd4hep::rec::CellID> tested_cells;
+    std::vector<std::pair<dd4hep::rec::CellID, float>> cell_charge;
+    findAllNeighborsInSensor(cellID, tested_cells, cell_charge, edep, hitPos, element);
+
+    // Create a new simhit for each cell with deposited energy
+    for (const auto& [testCellID, edep_cell] : cell_charge) {
+      auto globalCellPos = m_converter->position(testCellID);
+
+      edm4hep::MutableSimTrackerHit shared_hit = hit.clone();
+      shared_hit.setCellID(testCellID);
+      shared_hit.setEDep(edep_cell);
+      shared_hit.setPosition({globalCellPos.x() / dd4hep::mm, globalCellPos.y() / dd4hep::mm,
+                              globalCellPos.z() / dd4hep::mm});
+      sharedHits->push_back(shared_hit);
+    }
+
+  } // for simhits
+} // SiliconChargeSharing:process
+
+// Recursively find neighbors where a charge is deposited
+void SiliconChargeSharing::findAllNeighborsInSensor(
+    const dd4hep::rec::CellID testCellID, std::unordered_set<dd4hep::rec::CellID>& tested_cells,
+    std::vector<std::pair<dd4hep::rec::CellID, float>>& cell_charge, const float edep,
+    const dd4hep::Position hitPos, const dd4hep::DetElement* element) const {
+
+  // Tag cell as tested
+  tested_cells.insert(testCellID);
+
+  auto localPos = cell2LocalPosition(testCellID);
+
+  // Check if the cell is within the sensor boundaries
+  if (std::abs(localPos.x()) > m_x_range_map[element] ||
+      std::abs(localPos.y()) > m_y_range_map[element]) {
+    return;
+  }
+
+  auto segmentation = m_segmentation_map[element];
+
+  // cout the local position and hit position
+  auto xDimension = segmentation->gridSizeX();
+  auto yDimension = segmentation->gridSizeY();
+  // Calculate deposited energy in cell
+  float edepCell = energyAtCell(xDimension, yDimension, localPos, hitPos, edep);
+  if (edepCell < m_cfg.min_edep) {
+    return;
+  }
+
+  // Store cellID and deposited energy
+  cell_charge.push_back(std::make_pair(testCellID, edepCell));
+
+  // As there is charge in the cell, test the neighbors too
+  std::set<dd4hep::rec::CellID> testCellNeighbours;
+  segmentation->neighbours(testCellID, testCellNeighbours);
+
+  for (const auto& neighbourCell : testCellNeighbours) {
+    if (tested_cells.find(neighbourCell) == tested_cells.end()) {
+      findAllNeighborsInSensor(neighbourCell, tested_cells, cell_charge, edep, hitPos, element);
path: src/algorithms/digi/SiliconChargeSharing.cc
user: login: wdconinc
body: Thanks!
created_at: 2025-05-12T15:23:25Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-21T10:54:52Z
body: ### Capybara summary for PR 1763
- [rec_dis_10x100_minQ2=0_craterlake](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_10x100_minQ2=0_craterlake/index.html)
- [rec_dis_10x100_minQ2=1000_craterlake_tracking_only](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_10x100_minQ2=1000_craterlake_tracking_only/index.html)
- [rec_dis_18x275_minQ2=0_craterlake_18x275](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_18x275_minQ2=0_craterlake_18x275/index.html)
- [rec_dis_18x275_minQ2=1000_craterlake_18x275](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_18x275_minQ2=1000_craterlake_18x275/index.html)
- [rec_dis_5x41_minQ2=0_craterlake_5x41](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_5x41_minQ2=0_craterlake_5x41/index.html)
- [rec_e_1GeV_20GeV_craterlake](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_e_1GeV_20GeV_craterlake/index.html)
- [rec_pi_1GeV_20GeV_craterlake](https://eicrecon.epic-eic.org/pr/1763/capybara/rec_pi_1GeV_20GeV_craterlake/index.html)
<sup><sub>Last updated 2025-05-16T13:49-04:00 d7ab9829c48e7d5fdb9ed34b6b10762c0faa1734</sub></sup>

author_association: CONTRIBUTOR
user: login: veprbl
created_at: 2025-04-09T19:02:08Z
body: Need a rebase.
author_association: MEMBER
user: login: simonge
created_at: 2025-04-09T19:30:25Z
body: There are some changes in the BTOF charge sharing I need to understand/fix and discus whether the changes are actually improvements or not.
author_association: CONTRIBUTOR
user: login: ssedd1123
created_at: 2025-04-11T15:21:11Z
body: > There are some changes in the BTOF charge sharing I need to understand/fix and discus whether the changes are actually improvements or not.

What's the problem you're facing, if you don't mind me asking? I think it is a nice improvement over the original LGADChargeSharing. I've always considered testing cellID with position converter and see if it throws error is a bit of a hack. I did not know m_seg.neighbours exist at all. 
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-04-11T17:39:20Z
body: > What's the problem you're facing, if you don't mind me asking? I think it is a nice improvement over the original LGADChargeSharing. I've always considered testing cellID with position converter and see if it throws error is a bit of a hack. I did not know m_seg.neighbours exist at all.

It's the differences in the capybara I don't quite understand:
https://eicrecon.epic-eic.org/pr/1763/capybara/rec_dis_10x100_minQ2=0_craterlake/index.html#TOFBarrelADCTDC

I thought that the changes shouldn't make a difference to the number or charge of hits if the charge limit is set to 0. But it looks like there are significantly fewer hits being created.

The neighbours will only work if the sensor uses a segmentation in the geometry which may not always be the case (but probably always should be...). I don't believe this will account for the edges of sensors either, so a hit in the detector can probably spread outside the sensor.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-04-17T13:42:31Z
body: Looks like the problem comes from the MultiSegmentation used for the BTOF. The dd4hep neighbours function doesn't return any cellIDs when it is used. If it is possible to recover which sub-segmentation was used for the specific CellID this might be fixable but I haven't looked yet.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-04-22T11:52:11Z
body: Any help with the following compilation error would be appreciated:

```
/usr/bin/ld: ../../algorithms/digi/libalgorithms_digi.so: undefined reference to `dd4hep::SegmentationObject* dd4hep::detail::safe_cast<dd4hep::SegmentationObject>::cast<dd4hep::DDSegmentation::Segmentation>(dd4hep::DDSegmentation::Segmentation const*)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
gmake[2]: *** [src/tests/algorithms_test/CMakeFiles/algorithms_test.dir/build.make:388: src/tests/algorithms_test/algorithms_test] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:2959: src/tests/algorithms_test/CMakeFiles/algorithms_test.dir/all] Error 2
```

If I exclude the algorithms_test from the top level CMakeLists.txt it works fine despite algorithms_digi being included in other plugins.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-05-08T17:57:31Z
body: I believe this is ready for review, will see if there are no diffs in `TOFBarrelADCTDC` then could change
`edepCell < m_cfg.min_edep` to `edepCell <= m_cfg.min_edep` to remove 0s

It should be a bit more efficient and accessible than the current LGADChargeSharing algorithm but my original aim to make it more flexible might have failed, now requiring the segmentation to be `CartesianGridXY` and the volume to be a `dd4hep::Box`.

Filling the maps for each detector element would ideally be moved from `process` into `init` but I couldn't find a good way to go about that.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-05-09T12:55:54Z
body: I don't understand where the capybara diffs in `TaggerTrackerHitPulses` time and position come from.

The distributions in `TaggerTrackerHits` and `TaggerTrackerSharedHits` look correct and almost identical so simply swapping the input to the shared hits shouldn't effect the values this much.

Additionally the reference distribution for the `TaggerTrackerHitPulses` time is different to those run against other PRs.
author_association: CONTRIBUTOR
user: login: veprbl
created_at: 2025-05-09T18:06:50Z
body: > I don't understand where the capybara diffs in `TaggerTrackerHitPulses` time and position come from.

I also wasn't sure, but explained by the fact that you were feeding `TaggerTrackerSharedHits` to digitization instead of `TaggerTrackerHits`. I see you reverted it now. That is temporary until you have clustering working?
author_association: MEMBER
user: login: simonge
created_at: 2025-05-09T18:25:04Z
body: > > I don't understand where the capybara diffs in `TaggerTrackerHitPulses` time and position come from.
> 
> I also wasn't sure, but explained by the fact that you were feeding `TaggerTrackerSharedHits` to digitization instead of `TaggerTrackerHits`. I see you reverted it now. That is temporary until you have clustering working?

It was more a test to try and see if the positions and times were corrupted downstream in other algorithms. My other thought was that cloning the hit and editing the mutable version might cause issues but is appears the pulses still have the wrong positions and times reported in capybara. I will have to do some local tests to see if this happens. The difference in the reference distribution from this and other PRs surely shouldn't happen.

The clustering for LowQ2 hits should be working already, I just wanted to avoid introducing something that is more realistic but not fully calibrated yet until the real detector response is better understood. I will swap it back in for a future PR when it's been tuned a bit better, otherwise I might just forget and leave it.
author_association: CONTRIBUTOR
user: login: wdconinc
created_at: 2025-05-10T16:15:25Z
body: Fyi, this PR fixes the (last) multi-threading issues in EICrecon, and allows #975 to work.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-05-12T16:15:55Z
body: @wdconinc Any idea on this new memory leak I've introduced or how to debug it?
author_association: CONTRIBUTOR
user: login: wdconinc
created_at: 2025-05-12T16:38:06Z
body: > @wdconinc Any idea on this new memory leak I've introduced or how to debug it?

I'm guessing it came in through https://github.com/eic/epic/pull/842. I'm looking into it.
author_association: CONTRIBUTOR
user: login: simonge
created_at: 2025-05-14T11:07:01Z
body: I'm happier with how this looks now, if this can be approved.

Looks like the TOF digitization is closer to 10% now.
A bit off topic but is converting hits into measurements taking 35% of the computation or has that been combined with the CKF itself running somehow?

The change in the epic geometry changed the reference sample between PRs which caused me some confusion but did highlight the bugs in the pulse generation.

author_association: CONTRIBUTOR
user: login: wdconinc
created_at: 2025-05-14T12:36:01Z
body: > A bit off topic but is converting hits into measurements taking 35% of the computation or has that been combined with the CKF itself running somehow?

It's not always clear or consistent. JANA2 works by producing inputs for factories just-in-time, through calls to other factories. So, if a factory needs to get another factory to do something, then it will sit there and the extra time will be counted against it. If the inputs are already available when a next factory runs, it will not get that extra cost in running time. This all depends on the order in which factories are activated, and janatop has not enough info in its structures to subtract the time spent in supplier factories.

Looking at https://github.com/eic/EICrecon/actions/runs/14996201580/job/42131377031?pr=1763#step:7:1365, there are a few unambiguous lines possible:
- any digitization step that takes a non-negligible amount of time will show us as a link from raw hits to sim hits, and will be exclusive timing in the digitization itself,
- any link that starts from the JEventProcessorPodio is (at worst) the total time it takes to produce that output (but some time can have been accounted already in other links).

So, from that output, I'd agree that 10% is spent in creating TOFBarrelADCTDC, and all its inputs. It seems like 7.4% of time is spent inside the factory that created TOFBarrelPulses and all its inputs, and 2.3% is then spent inside TOFBarrelSmoothPulses (by which time the input will already have been created, apparently).
author_association: CONTRIBUTOR