Skip to content

New QA module to make cluster-state residual plots#4118

Merged
osbornjd merged 2 commits into
sPHENIX-Collaboration:masterfrom
rosstom2232:TPOTQA
Jan 17, 2026
Merged

New QA module to make cluster-state residual plots#4118
osbornjd merged 2 commits into
sPHENIX-Collaboration:masterfrom
rosstom2232:TPOTQA

Conversation

@rosstom2232

@rosstom2232 rosstom2232 commented Jan 17, 2026

Copy link
Copy Markdown
Contributor

comment: New QA module that lets the user check the residuals between the x, y, and z cluster and track state positions to monitor the quality of the track fitting for different conditions. Current available cuts are on nMVTX, nINTT, nTPC, track eta, track phi, track pT, and positive/negative charge. Any suggestions or comments are welcome!

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work for users)
  • Requiring change in macros repository (Please provide links to the macros pull request in the last section)
  • I am a member of GitHub organization of sPHENIX Collaboration, EIC, or ECCE (contact Chris Pinkenburg to join)

What kind of change does this PR introduce? (Bug fix, feature, ...)

TODOs (if applicable)

Links to other PRs in macros and calibration repositories (if applicable)

Summary

This PR introduces a new QA module, StateClusterResidualsQA, to monitor track-fitting quality in the sPHENIX tracking system by producing residual histograms between fitted track state positions and cluster positions (x, y, z) across tracking detectors.

Motivation / Context

Residual distributions between track states and cluster positions are key diagnostics for alignment, systematic biases, and track-fitting performance. This module provides configurable, per-track-population monitoring to help identify detector- and reconstruction-level issues during commissioning and routine QA.

Key Changes

  • New QA module: adds StateClusterResidualsQA (header + implementation) and registers sources in offline/QA/Tracking/Makefile.am.
  • Configuration API: builder-style methods to add multiple histogram configurations with selection cuts:
    • Per-detector cluster count ranges (MVTX, INTT, TPC)
    • Kinematic ranges: η, φ, pT
    • Track charge filtering (positive/negative)
  • Histogram management: creates triplets of 1D histograms (x, y, z residuals) per configuration and registers them with the QA histogram manager.
  • Event processing: for each track, counts per-layer clusters, applies selection cuts, computes residuals by matching track states to cluster global positions (via Acts geometry), and fills histograms.
  • Integration: added to the build system (Makefile.am) so the module is available to QA jobs.

Potential Risk Areas

  • IO / Outputs: New histograms registered with QA manager; ensure naming conventions align with downstream QA/monitoring macros and dashboards.
  • Reconstruction behavior: Module reads track states and cluster containers but does not modify reconstruction. However it assumes the presence and stability of SvtxTrackMap, TrkrClusterContainer, Acts geometry, and the QA histogram manager.
  • Null-pointer / lifecycle assumptions: InitRun validates required nodes, but a lack of defensive checks in process_event could lead to crashes if those nodes are absent or change during a run.
  • Thread-safety / concurrency: The module appears written for the classic (non-threaded) Fun4All event loop; thread-safety of shared histogram manager access and geometry lookups is not addressed.
  • Performance: Each matching track is iterated twice (counting states then filling residuals). For many configs or high-occupancy events this may add CPU overhead and memory pressure from many histograms.

Possible Future Improvements

  • Consolidate state-to-cluster matching to a single pass (cache matches) to reduce per-track overhead.
  • Add 2D residual or residual-vs-variable histograms (e.g., residual vs. φ, η, radius) for richer diagnostics.
  • Provide configuration validation and clearer histogram naming/documentation to ease integration with existing QA dashboards and macros.
  • Add defensive null checks and clearer error reporting to avoid silent failures in production runs.
  • Assess and, if needed, adapt for multi-threaded processing and profile performance on realistic workloads.

Note: AI-generated summaries can be wrong or miss details — please review the implementation and the assumptions about node lifetimes, histogram naming, and thread-safety before merging.

@coderabbitai

coderabbitai Bot commented Jan 17, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a new QA module, StateClusterResidualsQA, plus its header and Makefile entries. The module computes per-track state vs. cluster residuals, creates per-config histograms, and integrates with the QA histogram manager during InitRun/process_event/EndRun.

Changes

Cohort / File(s) Summary
Build configuration
offline/QA/Tracking/Makefile.am
Exported header StateClusterResidualsQA.h added to pkginclude_HEADERS; implementation StateClusterResidualsQA.cc added to libtrackingqa_la_SOURCES.
QA module interface
offline/QA/Tracking/StateClusterResidualsQA.h
New ResidualHistConfig struct and StateClusterResidualsQA class declared. Fluent configuration methods, histogram storage vectors, and SubsysReco lifecycle hooks (InitRun, process_event, EndRun) exposed.
QA module implementation
offline/QA/Tracking/StateClusterResidualsQA.cc
Implements constructor, InitRun, process_event, createHistos, and EndRun. Retrieves track map, cluster container, geometry, and QA histogram manager; counts per-layer clusters, applies per-config selections, computes state-to-cluster residuals, and fills per-config x/y/z histograms.

Sequence Diagram(s)

sequenceDiagram
    participant EventLoop as Event Loop
    participant QAMod as StateClusterResidualsQA
    participant PHNode as PHCompositeNode
    participant TrackMap as SvtxTrackMap
    participant Clusters as TrkrClusterContainer
    participant Geom as ActsGeometry
    participant HistMgr as QAHistogramManager
    participant Hist as Histograms

    EventLoop->>QAMod: InitRun(top_node)
    QAMod->>PHNode: get_node("SvtxTrackMap")
    QAMod->>PHNode: get_node("TRKR_CLUSTER")
    QAMod->>PHNode: get_node("ActsGeometry")
    QAMod->>HistMgr: acquire manager
    QAMod->>QAMod: createHistos()
    QAMod->>HistMgr: register histograms

    loop per event
      EventLoop->>QAMod: process_event(top_node)
      QAMod->>TrackMap: iterate tracks
      loop per track
        QAMod->>TrackMap: access track states
        QAMod->>Clusters: lookup cluster for state
        QAMod->>Geom: convert cluster to global position
        alt selection matched
          QAMod->>Hist: fill residual histograms (x,y,z)
        end
      end
    end

    EventLoop->>QAMod: EndRun(runnumber)
    QAMod->>HistMgr: finalize (no extra processing)
Loading
✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Comment on lines +113 to +118
for (const auto& cfg : m_pending)
{
m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x"))));
m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y"))));
m_histograms_z.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z"))));
}

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.

⚠️ Potential issue | 🟠 Major

Missing null check on retrieved histogram pointers.

hm->getHisto() may return nullptr if the histogram name lookup fails. These pointers are stored and later dereferenced unconditionally in process_event() (lines 195-197), which would cause a crash.

Consider validating each histogram pointer after retrieval:

Proposed fix
  for (const auto& cfg : m_pending)
  {
-   m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x"))));
-   m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y"))));
-   m_histograms_z.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z"))));
+   auto* hx = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x")));
+   auto* hy = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y")));
+   auto* hz = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z")));
+   if (!hx || !hy || !hz)
+   {
+     std::cout << PHWHERE << "\n"
+               << "\tCould not retrieve histogram for config: " << cfg.name << "\n"
+               << "\tAborting\n" << std::endl;
+     return Fun4AllReturnCodes::ABORTRUN;
+   }
+   m_histograms_x.push_back(hx);
+   m_histograms_y.push_back(hy);
+   m_histograms_z.push_back(hz);
  }

Comment on lines +185 to +198
auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}

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.

⚠️ Potential issue | 🔴 Critical

Null pointer dereference: cluster used before null check.

findCluster() may return nullptr, but cluster is passed to geometry->getGlobalPosition() on line 189 before the null check on line 193. This will cause undefined behavior when the cluster is not found.

Proposed fix
          auto *cluster = cluster_map->findCluster(state->get_cluskey());
+         if (!cluster)
+         {
+           continue;
+         }
          float state_x = state->get_x();
          float state_y = state->get_y();
          float state_z = state->get_z();
          Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
          float cluster_x = glob.x();
          float cluster_y = glob.y();
          float cluster_z = glob.z();
-         if (cluster)
-         {
-           m_histograms_x[h]->Fill(state_x - cluster_x);
-           m_histograms_y[h]->Fill(state_y - cluster_y);
-           m_histograms_z[h]->Fill(state_z - cluster_z);
-         }
+         m_histograms_x[h]->Fill(state_x - cluster_x);
+         m_histograms_y[h]->Fill(state_y - cluster_y);
+         m_histograms_z[h]->Fill(state_z - cluster_z);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
auto *cluster = cluster_map->findCluster(state->get_cluskey());
if (!cluster)
{
continue;
}
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);

Comment on lines +56 to +101
StateClusterResidualsQA& setNMvtx(int min, int max)
{
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}

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.

⚠️ Potential issue | 🟠 Major

Undefined behavior: m_pending.back() called on potentially empty vector.

All fluent setter methods (setNMvtx, setNIntt, setNTpc, setPhiRange, setEtaRange, setPtRange, setPositiveTracks, setNegativeTracks) access m_pending.back() without verifying that m_pending is non-empty. If a user calls any of these before addHistogram(), this results in undefined behavior.

Consider adding an assertion or guard:

Proposed fix (example for setNMvtx)
  StateClusterResidualsQA& setNMvtx(int min, int max)
  {
+   assert(!m_pending.empty() && "Must call addHistogram() before setNMvtx()");
    m_pending.back().min_mvtx_clusters = min;
    m_pending.back().max_mvtx_clusters = max;
    return *this;
  }

Apply similar guards to all other setter methods, or extract a helper that returns a reference with the check.

@sphenix-jenkins-ci

Copy link
Copy Markdown

Build & test report

Report for commit d1152d0f8d158c3f7abd88006dfda222a3c0ffc3:
Jenkins passed


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

♻️ Duplicate comments (2)
offline/QA/Tracking/StateClusterResidualsQA.cc (2)

185-198: Null pointer dereference: cluster passed to getGlobalPosition before null check.

findCluster() may return nullptr, but the pointer is used on line 189 before it is validated on line 193. This causes undefined behavior when a cluster is not found.

Move the null check immediately after findCluster().


113-118: Missing null check on retrieved histogram pointers.

hm->getHisto() may return nullptr if lookup fails. These pointers are later dereferenced unconditionally in process_event(), causing a crash.

Validate each histogram pointer after retrieval and return ABORTRUN on failure.

Comment on lines +125 to +127
auto* track_map = findNode::getClass<SvtxTrackMap>(top_node, m_track_map_node_name);
auto *cluster_map = findNode::getClass<TrkrClusterContainer>(top_node, m_clusterContainerName);
auto *geometry = findNode::getClass<ActsGeometry>(top_node, "ActsGeometry");

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.

⚠️ Potential issue | 🟠 Major

No null checks on re-fetched node pointers.

These pointers are dereferenced unconditionally (e.g., *track_map at line 129, cluster_map->findCluster at line 185, geometry->getGlobalPosition at line 189). If the nodes become unavailable after InitRun, this will crash.

While the framework typically guarantees node persistence, defensive null checks would prevent silent corruption if assumptions are violated.

Proposed fix
   auto* track_map = findNode::getClass<SvtxTrackMap>(top_node, m_track_map_node_name);
   auto *cluster_map = findNode::getClass<TrkrClusterContainer>(top_node, m_clusterContainerName);
   auto *geometry = findNode::getClass<ActsGeometry>(top_node, "ActsGeometry");
+
+  if (!track_map || !cluster_map || !geometry)
+  {
+    return Fun4AllReturnCodes::ABORTEVENT;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto* track_map = findNode::getClass<SvtxTrackMap>(top_node, m_track_map_node_name);
auto *cluster_map = findNode::getClass<TrkrClusterContainer>(top_node, m_clusterContainerName);
auto *geometry = findNode::getClass<ActsGeometry>(top_node, "ActsGeometry");
auto* track_map = findNode::getClass<SvtxTrackMap>(top_node, m_track_map_node_name);
auto *cluster_map = findNode::getClass<TrkrClusterContainer>(top_node, m_clusterContainerName);
auto *geometry = findNode::getClass<ActsGeometry>(top_node, "ActsGeometry");
if (!track_map || !cluster_map || !geometry)
{
return Fun4AllReturnCodes::ABORTEVENT;
}

Comment on lines +160 to +202
int h = 0;
for (const auto& cfg : m_pending)
{
if (cfg.charge != 0)
{
if ((cfg.charge < 0) && track->get_positive_charge())
{
continue;
}
if ((cfg.charge > 0) && !(track->get_positive_charge()))
{
continue;
}
}
if (cfg.min_mvtx_clusters <= counters[TrkrDefs::mvtxId] && cfg.max_mvtx_clusters >= counters[TrkrDefs::mvtxId]
&& cfg.min_intt_clusters <= counters[TrkrDefs::inttId] && cfg.max_intt_clusters >= counters[TrkrDefs::inttId]
&& cfg.min_tpc_clusters <= counters[TrkrDefs::tpcId] && cfg.max_tpc_clusters >= counters[TrkrDefs::tpcId]
&& cfg.phi_min <= track_phi && cfg.phi_max >= track_phi
&& cfg.eta_min <= track_eta && cfg.eta_max >= track_eta
&& cfg.pt_min <= track_pt && cfg.pt_max >= track_pt)
{
for (auto const& [path_length, state] : range_adaptor(track->begin_states(), track->end_states()))
{
if (path_length == 0) { continue; }

auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
}
}
++h;
}

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.

⚠️ Potential issue | 🔴 Critical

Critical bug: Histogram index h becomes misaligned after charge-filter continue.

When the charge filter (lines 165-172) triggers continue, the loop skips the ++h at line 201. Subsequent configs will then use incorrect histogram indices, filling the wrong histograms.

The index must be incremented regardless of whether the track passes the charge filter.

Proposed fix
     int h = 0;
     for (const auto& cfg : m_pending)
     {
+      // Increment index early so continue statements don't skip it
+      int current_h = h++;
+
       if (cfg.charge != 0)
       {
         if ((cfg.charge < 0) && track->get_positive_charge())
         {
-          continue;
+          continue;  // h already incremented above
         }
         if ((cfg.charge > 0) && !(track->get_positive_charge()))
         {
           continue;
         }
       }
       if (cfg.min_mvtx_clusters <= counters[TrkrDefs::mvtxId] && cfg.max_mvtx_clusters >= counters[TrkrDefs::mvtxId]
           && cfg.min_intt_clusters <= counters[TrkrDefs::inttId] && cfg.max_intt_clusters >= counters[TrkrDefs::inttId]
           && cfg.min_tpc_clusters <= counters[TrkrDefs::tpcId] && cfg.max_tpc_clusters >= counters[TrkrDefs::tpcId]
           && cfg.phi_min <= track_phi && cfg.phi_max >= track_phi
           && cfg.eta_min <= track_eta && cfg.eta_max >= track_eta
           && cfg.pt_min <= track_pt && cfg.pt_max >= track_pt)
       {
         for (auto const& [path_length, state] : range_adaptor(track->begin_states(), track->end_states()))
         {
           if (path_length == 0) { continue; }

           auto *cluster = cluster_map->findCluster(state->get_cluskey());
+          if (!cluster)
+          {
+            continue;
+          }
           float state_x = state->get_x();
           float state_y = state->get_y();
           float state_z = state->get_z();
           Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
           float cluster_x = glob.x();
           float cluster_y = glob.y();
           float cluster_z = glob.z();
-          if (cluster)
-          {
-            m_histograms_x[h]->Fill(state_x - cluster_x);
-            m_histograms_y[h]->Fill(state_y - cluster_y);
-            m_histograms_z[h]->Fill(state_z - cluster_z);
-          }
+          m_histograms_x[current_h]->Fill(state_x - cluster_x);
+          m_histograms_y[current_h]->Fill(state_y - cluster_y);
+          m_histograms_z[current_h]->Fill(state_z - cluster_z);
         }
       } 
-      ++h;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int h = 0;
for (const auto& cfg : m_pending)
{
if (cfg.charge != 0)
{
if ((cfg.charge < 0) && track->get_positive_charge())
{
continue;
}
if ((cfg.charge > 0) && !(track->get_positive_charge()))
{
continue;
}
}
if (cfg.min_mvtx_clusters <= counters[TrkrDefs::mvtxId] && cfg.max_mvtx_clusters >= counters[TrkrDefs::mvtxId]
&& cfg.min_intt_clusters <= counters[TrkrDefs::inttId] && cfg.max_intt_clusters >= counters[TrkrDefs::inttId]
&& cfg.min_tpc_clusters <= counters[TrkrDefs::tpcId] && cfg.max_tpc_clusters >= counters[TrkrDefs::tpcId]
&& cfg.phi_min <= track_phi && cfg.phi_max >= track_phi
&& cfg.eta_min <= track_eta && cfg.eta_max >= track_eta
&& cfg.pt_min <= track_pt && cfg.pt_max >= track_pt)
{
for (auto const& [path_length, state] : range_adaptor(track->begin_states(), track->end_states()))
{
if (path_length == 0) { continue; }
auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
}
}
++h;
}
int h = 0;
for (const auto& cfg : m_pending)
{
// Increment index early so continue statements don't skip it
int current_h = h++;
if (cfg.charge != 0)
{
if ((cfg.charge < 0) && track->get_positive_charge())
{
continue;
}
if ((cfg.charge > 0) && !(track->get_positive_charge()))
{
continue;
}
}
if (cfg.min_mvtx_clusters <= counters[TrkrDefs::mvtxId] && cfg.max_mvtx_clusters >= counters[TrkrDefs::mvtxId]
&& cfg.min_intt_clusters <= counters[TrkrDefs::inttId] && cfg.max_intt_clusters >= counters[TrkrDefs::inttId]
&& cfg.min_tpc_clusters <= counters[TrkrDefs::tpcId] && cfg.max_tpc_clusters >= counters[TrkrDefs::tpcId]
&& cfg.phi_min <= track_phi && cfg.phi_max >= track_phi
&& cfg.eta_min <= track_eta && cfg.eta_max >= track_eta
&& cfg.pt_min <= track_pt && cfg.pt_max >= track_pt)
{
for (auto const& [path_length, state] : range_adaptor(track->begin_states(), track->end_states()))
{
if (path_length == 0) { continue; }
auto *cluster = cluster_map->findCluster(state->get_cluskey());
if (!cluster)
{
continue;
}
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
m_histograms_x[current_h]->Fill(state_x - cluster_x);
m_histograms_y[current_h]->Fill(state_y - cluster_y);
m_histograms_z[current_h]->Fill(state_z - cluster_z);
}
}
}

@sphenix-jenkins-ci

Copy link
Copy Markdown

Build & test report

Report for commit 6c11c5d5a51c7dcb4eb0ff7fcf70a64280d90d90:
Jenkins passed


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

@osbornjd osbornjd merged commit 7030fb8 into sPHENIX-Collaboration:master Jan 17, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants