Skip to content

Scfi Img CrossSystem Topo Clustering (#2861) - #2865

Open
AkshayaVijay wants to merge 6 commits into
mainfrom
Hybrid-Clustering-Algorithm-for-BIC
Open

Scfi Img CrossSystem Topo Clustering (#2861)#2865
AkshayaVijay wants to merge 6 commits into
mainfrom
Hybrid-Clustering-Algorithm-for-BIC

Conversation

@AkshayaVijay

Copy link
Copy Markdown
Contributor

Briefly, what does this PR introduce? Please link to any relevant presentations or discussions.

Allows clustering of ScFi and Imaging Hits together based on the Topological Clustering Algorithm

What is the urgency of this PR?

  • High (please describe reason below)
  • Medium
  • Low

What kind of change does this PR introduce?

  • Bug fix (issue #__)
  • New feature (issue #__)
  • Optimization (issue #__)
  • Updated parameters, constants (issue #__)
  • Updated documentation
  • other: __

Please check if any of the following apply

  • This PR requires changes to geometry (epic PR: __)
  • This PR requires changes to EDM4eic (EDM PR: __)
  • This PR introduces breaking changes. Please describe changes users need to make below.
  • This PR changes default behavior. Please describe changes below.
  • AI was used in preparing this PR. Please describe usage below.

Briefly, what does this PR introduce? Please link to any relevant presentations or discussions.

What is the urgency of this PR?

  • High (please describe reason below)
  • Medium
  • Low

What kind of change does this PR introduce?

  • Bug fix (issue #__)
  • New feature (issue #__)
  • Optimization (issue #__)
  • Updated parameters, constants (issue #__)
  • Updated documentation
  • other: __

Please check if any of the following apply

  • This PR requires changes to geometry (epic PR: __)
  • This PR requires changes to EDM4eic (EDM PR: __)
  • This PR introduces breaking changes. Please describe changes users need to make below.
  • This PR changes default behavior. Please describe changes below.
  • AI was used in preparing this PR. Please describe usage below.

### Briefly, what does this PR introduce? Please link to any relevant
presentations or discussions.

### What is the urgency of this PR?
- [ ] High (please describe reason below)
- [x] Medium
- [ ] Low

### What kind of change does this PR introduce?
- [ ] Bug fix (issue #__)
- [x] New feature (issue #__)
- [ ] Optimization (issue #__)
- [ ] Updated parameters, constants (issue #__)
- [ ] Updated documentation
- [ ] other: __

### Please check if any of the following apply
- [ ] This PR requires changes to geometry (epic PR: __)
- [ ] This PR requires changes to EDM4eic (EDM PR: __)
- [ ] This PR introduces breaking changes. Please describe changes users
need to make below.
- [ ] This PR changes default behavior. Please describe changes below.
- [ ] AI was used in preparing this PR. Please describe usage below.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Wouter Deconinck <wdconinc@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables topological clustering across SciFi and Imaging calorimeter hits (notably in BEMC) by introducing combined hit collections and extending the topo clustering + CoG reconstruction logic to be “system-aware” via DD4hep cellID decoding.

Changes:

  • Add readout fields to clustering/reco configs and use DD4hep IDDescriptor to decode system for cross-system / system-specific behavior.
  • Extend ImagingTopoCluster to cluster per system and optionally merge SciFi+Imaging clusters, and add combined hit/link/association collections in the BEMC plugin.
  • Update algorithm unit tests to provide the new readout configuration.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/tests/algorithms_test/particle_flow_TrackProtoClusterMatchPromoter.cc Updates test config to set readout for CoG cluster reco.
src/tests/algorithms_test/calorimetry_ImagingTopoCluster.cc Updates topo clustering test to set readout and new mode/distance fields.
src/tests/algorithms_test/calorimetry_CalorimeterClusterRecoCoG.cc Updates CoG clustering test to set readout.
src/detectors/BEMC/BEMC.cc Adds merged SciFi+Imaging hit/link/association collections and runs topo clustering + CoG on the combined collection.
src/algorithms/calorimetry/ImagingTopoClusterConfig.h Expands config to include readout, additional system-specific parameters, and new xz layer mode.
src/algorithms/calorimetry/ImagingTopoCluster.h Adds geometry/ID decoding state and cross-system neighbor helper declarations.
src/algorithms/calorimetry/ImagingTopoCluster.cc Implements system-aware grouping/merging and readout-based ID decoding.
src/algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h Adds readout to support system-aware behavior.
src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h Adds geometry/ID decoding state used by system-aware weighting.
src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc Implements system-aware energy/position weighting based on decoded system IDs.
Suppressed comments (2)

src/algorithms/calorimetry/ImagingTopoCluster.cc:337

  • Typo in the exception message: it says the expected tuple is "(x_dist, y_dist, y_dist)" but the third component is z_dist.
      const std::string msg =
          "Expected 3 values (x_dist, y_dist, y_dist) for ScFi_diffLayerDistXYZ";
      error(msg);

src/algorithms/calorimetry/ImagingTopoCluster.cc:432

  • Typo in the exception message: it says the expected tuple is "(x_dist, y_dist, y_dist)" but the third component is z_dist.
      const std::string msg = "Expected 3 values (x_dist, y_dist, y_dist) for Img_diffLayerDistXYZ";

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +118 to +121
inline int getSystemID(const edm4eic::CalorimeterHit& hit, const dd4hep::IDDescriptor& m_idSpec) {
static thread_local auto* sys_field = m_idSpec.field("system");
return sys_field->value(hit.getCellID());
}
Comment on lines +151 to +162
bool hasSciFi = false;
bool hasImaging = false;

for (auto& hit : pcl.getHits()) {
if (isSciFiHit(hit, m_idSpec))
hasSciFi = true;
if (isImagingHit(hit, m_idSpec))
hasImaging = true;
}

bool specialMode = hasSciFi && hasImaging;

Comment on lines +184 to +187

totalE += energy;
time += (hit.getTime() - time) * energy / totalE;

Comment on lines +49 to +56
m_detector = algorithms::GeoSvc::instance().detector();
if (!m_detector) {
error("Failed to get detector from GeometrySvc for readout '{}'", m_cfg.readout);
throw std::runtime_error("Detector not available");
}

m_idSpec = m_detector->readout(m_cfg.readout).idSpec();

case ImagingTopoClusterConfig::ELayerMode::xyz:
if (m_cfg.diffLayerDistXYZ.size() != 3) {
const std::string msg = "Expected 3 values (x_dist, y_dist, z_dist) for diffLayerDistXYZ";
const std::string msg = "Expected 3 values (x_dist, y_dist, y_dist) for diffLayerDistXYZ";
Comment on lines +113 to 115
} else if (s == "xz" or s == "2") {
layerMode = ImagingTopoClusterConfig::ELayerMode::xz;
} else if (s == "xyz" or s == "3") {

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (1/1)

Comment thread src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc Outdated
Comment thread src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc Outdated
This PR applies the include-what-you-use fixes as suggested by
https://github.com/eic/EICrecon/actions/runs/31633457160.
Please merge this PR into the branch
`Hybrid-Clustering-Algorithm-for-BIC`
to resolve failures in PR #2865.

Auto-generated by [create-pull-request][1]

[1]: https://github.com/peter-evans/create-pull-request

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 12, 2026 20:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (6)

src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc:120

  • getSystemID caches sys_field as thread_local and dereferences it without a null check. This can crash when the configured readout (or IDSpec) doesn't have a system field, and the thread_local cache can also become stale if different instances use different IDDescriptors on the same thread.
inline int getSystemID(const edm4eic::CalorimeterHit& hit, const dd4hep::IDDescriptor& m_idSpec) {
  static thread_local auto* sys_field = m_idSpec.field("system");
  return sys_field->value(hit.getCellID());
}

src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc:186

  • When specialMode is enabled, some hits can contribute energy = 0. If the first hits processed have zero energy, totalE stays 0 and energy / totalE becomes 0/0, producing NaNs/undefined behavior in the incremental time update.
    totalE += energy;
    time += (hit.getTime() - time) * energy / totalE;

src/algorithms/calorimetry/ImagingTopoCluster.cc:474

  • process() currently returns early when the configured IDSpec lacks a system field. Combined with init() unconditionally doing m_detector->readout(m_cfg.readout) this makes readout/system effectively mandatory and will cause existing configurations to stop producing clusters (e.g. ZDC.cc:190-205 and FHCAL.cc:98-120 configure ImagingTopoCluster_factory without .readout). Either update those callers to set .readout, or add a backward-compatible fallback path when system isn't available.
  auto* sys_field = m_idSpec.field("system");
  if (!sys_field) {
    error("Field 'system' not found in IDSpec for readout {}", m_cfg.readout);
    return;
  }

src/algorithms/calorimetry/ImagingTopoCluster.cc:220

  • Typo in error message: the third component should be z_dist, not y_dist (this message is user-facing and currently misleading).
      const std::string msg = "Expected 3 values (x_dist, y_dist, y_dist) for diffLayerDistXYZ";

src/algorithms/calorimetry/ImagingTopoCluster.cc:339

  • Typo in error message: the third component should be z_dist, not y_dist (this message is user-facing and currently misleading).
          "Expected 3 values (x_dist, y_dist, y_dist) for ScFi_diffLayerDistXYZ";
      error(msg);

src/algorithms/calorimetry/ImagingTopoCluster.cc:434

  • Typo in error message: the third component should be z_dist, not y_dist (this message is user-facing and currently misleading).
      const std::string msg = "Expected 3 values (x_dist, y_dist, y_dist) for Img_diffLayerDistXYZ";

Comment on lines +618 to +623
// Check against all ScFi clusters
for (size_t idx_ScFi : ScFi) {

const auto& cl_ScFi = all_clusters[idx_ScFi];
bool neighbor_found = false;

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Clang-Tidy found issue(s) with the introduced code (1/1)

Comment thread src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc Outdated
Comment thread src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc Outdated
Copilot AI review requested due to automatic review settings August 14, 2026 18:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (6)

src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc:188

  • In specialMode, Imaging hits are forced to contribute energy = 0, so the first few iterations can leave totalE == 0 and trigger 0/0 in the running time update (time += ... * energy / totalE), producing NaN/UB.
      energy = hit.getEnergy() * weight;
    }

    totalE += energy;
    time += (hit.getTime() - time) * energy / totalE;

src/algorithms/calorimetry/ImagingTopoCluster.cc:58

  • init() unconditionally does m_detector->readout(m_cfg.readout).idSpec(). Since ImagingTopoClusterConfig::readout defaults to an empty string, this will fail at runtime with a less actionable DD4hep error when callers forget to set it.
void ImagingTopoCluster::init() {

  m_detector = algorithms::GeoSvc::instance().detector();
  if (!m_detector) {
    error("Failed to get detector from GeometrySvc for readout '{}'", m_cfg.readout);
    throw std::runtime_error("Detector not available");
  }

  m_idSpec = m_detector->readout(m_cfg.readout).idSpec();

src/algorithms/calorimetry/ImagingTopoCluster.cc:223

  • The exception text for diffLayerDistXYZ says "(x_dist, y_dist, y_dist)" but the third component is z. This makes configuration/debugging confusing.
  case ImagingTopoClusterConfig::ELayerMode::xyz:
    if (m_cfg.diffLayerDistXYZ.size() != 3) {
      const std::string msg = "Expected 3 values (x_dist, y_dist, y_dist) for diffLayerDistXYZ";
      error(msg);
      throw std::runtime_error(msg);
    }

src/algorithms/calorimetry/ImagingTopoCluster.cc:623

  • Cross-system merging can duplicate the same ScFi cluster into multiple merged outputs: used_ScFi is set but never used to skip already-merged ScFi clusters in the inner loop, so later Img clusters can merge the same ScFi hits again.
      // Check against all ScFi clusters
      for (size_t idx_ScFi : ScFi) {

        const auto& cl_ScFi = all_clusters[idx_ScFi];
        bool neighbor_found = false;

src/algorithms/calorimetry/ImagingTopoCluster.cc:474

  • If the configured readout does not contain a system field, the algorithm currently logs an error and silently returns without producing any proto-clusters. This is easy to miss and turns a configuration problem into an empty-output failure mode.
  auto* sys_field = m_idSpec.field("system");
  if (!sys_field) {
    error("Field 'system' not found in IDSpec for readout {}", m_cfg.readout);
    return;
  }

src/algorithms/calorimetry/ImagingTopoCluster.cc:627

  • The cross-system merge checks every hit in every Img cluster against every hit in every ScFi cluster (nested loops over clusters and hits). In high-occupancy events this can become very expensive (worst-case O(N^2) in hits). Consider precomputing per-cluster bounding boxes / representative points or building a spatial index to shortlist candidate cluster pairs before doing hit-by-hit checks.
        for (auto h1 : cl_Img) {
          for (auto h2 : cl_ScFi) {
            if (cross_system_is_neighbour((*hits)[h1], (*hits)[h2])) {
              debug("  Cross system neighbour: Img cluster {} hit {}  <->  ScFi cluster {} hit {}",

Comment on lines +117 to +120
inline int getSystemID(const edm4eic::CalorimeterHit& hit, const dd4hep::IDDescriptor& m_idSpec) {
static thread_local const auto* sys_field = m_idSpec.field("system");
return sys_field->value(hit.getCellID());
}
@github-actions
github-actions Bot dismissed stale reviews from themself August 14, 2026 18:59

No Clang-Tidy warnings found so I assume my comments were addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants