Skip to content

KFParticle Track to Calo Matching QA#4251

Open
viewolfe321 wants to merge 1 commit intosPHENIX-Collaboration:masterfrom
viewolfe321:20260420_KFPtrack2CaloQA
Open

KFParticle Track to Calo Matching QA#4251
viewolfe321 wants to merge 1 commit intosPHENIX-Collaboration:masterfrom
viewolfe321:20260420_KFPtrack2CaloQA

Conversation

@viewolfe321
Copy link
Copy Markdown
Contributor

@viewolfe321 viewolfe321 commented Apr 20, 2026

This PR adds software that creates QA histograms for track to calo matching, focused on photon conversion.

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, ...)

This PR adds new software to be used with KFParticle and for data QA.
I describe how this code works and what it does here.

TODOs (if applicable)

This software needs more testing, and there is at least one known bug: If this module and the general KFParticle QA are both run in the same macro, either the macro will crash or the KFP container accessed by this module will be empty. I'm not sure how to resolve this problem, so I am asking the HF conveners for input.

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

KFParticle Track-to-Calorimeter Matching QA

Motivation

This PR introduces a new QA module to characterize track-to-calorimeter matching performance for KFParticle-reconstructed particles, with particular emphasis on photon conversion pair analysis. The module produces histograms tracking the quality of SVTX track projections to EMCal clusters, providing essential diagnostic information for reconstruction efficiency studies.

Key Changes

  • New QA subsystem: QAKFParticleTracktoCalo (SubsysReco module, ~1200 lines total)

    • Loads KFParticle containers, SVTX tracks, and calorimeter geometry/clusters
    • Iterates over reconstructed KFParticle mothers and recovers daughter tracks
    • Applies optional kinematic cuts: daughter angular separation, MVTX hit limits, vertex consistency
    • Matches daughter tracks to EMCal clusters via configurable delta-z (–10 to +10 cm) and delta-phi (±0.15 rad) thresholds
    • Fills per-daughter and correlation histograms: momentum/energy, cluster residuals (Δη, Δφ, Δz), detector hit counts, and mother mass vs. energy-over-momentum
  • Configuration flexibility: Setters for all matching thresholds (track-cluster distance cuts, cluster energy minimum, projection radius), optional angle cuts between daughter tracks, maximum MVTX hits per daughter, and histogram output prefix

  • Dual output modes: Via QAHistManager (default) or standalone ROOT file (with run-number suffix)

  • Build system: Standard Automake/Autoconf configuration with explicit dependencies on KFParticle, calobase, calo_io, CLHEP, and fun4all libraries

Potential Risk Areas

  • Critical known issue: Running this module simultaneously with the general KFParticle QA module causes either macro crashes or returns an empty KFParticle container. This suggests a container lifecycle or node-access conflict that requires investigation and resolution before production use.
  • Container robustness: Multiple fallback attempts for cluster/tower node access suggest potential fragility; production stability depends on node availability and naming consistency.
  • Parameter tuning: Default matching cuts (±10 cm in z, ±0.15 rad in φ) and cluster energy threshold (200 MeV) require validation against physics performance expectations; cuts are currently applied uniformly across all daughter charges and detector configurations.
  • Performance: Matching algorithm runs O(daughters × clusters) per event; scalability with high-multiplicity events not characterized.

Possible Future Improvements

  • Resolve the container-sharing conflict with other KFParticle QA modules before merging to avoid blocking other analyses
  • Add charge-dependent or detector-specific matching cut strategies
  • Include track chi-square/quality metrics in matching decision logic
  • Document matching algorithm assumptions and provide reference physics performance benchmarks
  • Consider GPU acceleration or cluster pre-filtering for improved scalability

Note: AI-assisted analysis used to interpret code structure and implementation details. Manual verification of matching algorithms, cut thresholds, and container access patterns recommended before deployment.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

A new QA module QAKFParticleTracktoCalo is added to the Fun4All framework. This module performs track-to-calorimeter matching for KFParticle decay daughters, applying kinematic cuts and collecting residual/correlation histograms for quality assurance. Build system files (Makefile, configure, autogen.sh) are included.

Changes

Cohort / File(s) Summary
Build System Configuration
offline/QA/KFParticle_Track2Calo/Makefile.am, offline/QA/KFParticle_Track2Calo/configure.ac, offline/QA/KFParticle_Track2Calo/autogen.sh
Autotools build setup for the QA module: Makefile defines library target with ROOT and Fun4All dependencies; configure.ac sets compiler flags and CINTDEFS; autogen.sh runs aclocal, libtoolize, automake, and autoconf.
QA Module Declaration
offline/QA/KFParticle_Track2Calo/QAKFParticleTracktoCalo.h
New SubsysReco class with public API for configuring track-cluster matching cuts (delta-z/phi thresholds, cluster energy, projection radius), daughter-angle and hit filters, histogram naming, and output file handling. Declares histogram pointers and internal state for matching workflow.
QA Module Implementation
offline/QA/KFParticle_Track2Calo/QAKFParticleTracktoCalo.cc
Core event-processing logic: loads KFParticle, track, and cluster containers; iterates KFParticle mothers; reconstructs daughters; applies kinematic cuts and EMCal matching; fills residual/correlation histograms for positive/negative charges. Outputs histograms to ROOT file with optional QA histogram manager integration.

Sequence Diagram(s)

sequenceDiagram
    participant Event as Event Loop
    participant Module as QAKFParticleTracktoCalo
    participant NodeTree as Fun4All Node Tree
    participant KFP as KFParticle Container
    participant EMCal as EMCal Clusters
    participant Histos as Histograms
    
    Event->>Module: InitRun()
    Module->>NodeTree: Locate geometry/tower containers
    Module->>Histos: SetUpHistos()
    
    Event->>Module: process_event()
    Module->>NodeTree: LoadNodes() - fetch SvtxTrackMap, KFParticle, clusters
    Module->>KFP: Validate KFParticle container present
    Module->>KFP: Iterate mothers
    
    loop For each mother
        Module->>KFP: GetVectorOfDaughters()
        loop For each daughter
            Module->>KFP: Apply angle/kinematic cuts
            Module->>KFP: Check vertex ID consistency
            Module->>EMCal: GetMatchedCluster(track state → cluster)
            alt Cluster matched
                Module->>Histos: Fill residual plots (Δz, Δφ, energy)
                Module->>Histos: Fill correlation plots
            end
        end
    end
    
    Event->>Module: End()
    Module->>Histos: Write to ROOT file (if not using histogram manager)
    
    Event->>Module: EndEvent()
    Module->>Module: Clear event state
Loading

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4918c2db-a194-47aa-bcc0-5ec372c25c21

📥 Commits

Reviewing files that changed from the base of the PR and between e1806f9 and cdc4af2.

📒 Files selected for processing (5)
  • offline/QA/KFParticle_Track2Calo/Makefile.am
  • offline/QA/KFParticle_Track2Calo/QAKFParticleTracktoCalo.cc
  • offline/QA/KFParticle_Track2Calo/QAKFParticleTracktoCalo.h
  • offline/QA/KFParticle_Track2Calo/autogen.sh
  • offline/QA/KFParticle_Track2Calo/configure.ac

Comment on lines +21 to +27
libQAKFParticleTracktoCalo_la_LDFLAGS = \
-L$(libdir) \
-L$(OFFLINE_MAIN)/lib \
-lKFParticle \
-lcalo_io \
-lCLHEP \
-lfun4all
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm how peer Makefile.am files declare link libraries vs LDFLAGS,
# and which libraries export the symbols/headers this module uses.
set -e
fd -a 'Makefile.am' offline/QA | xargs -I{} sh -c 'echo "=== {} ==="; cat {}' | sed -n '1,2000p'
echo "--- headers that declare the classes we depend on ---"
for sym in KFParticle_Container SvtxTrackMap ActsGeometry SvtxVertexMap QAHistManagerDef TrkrClusterContainer; do
  echo "### $sym"
  rg -n --no-heading -g '*.h' "class\s+$sym\b" offline || true
done

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 32575


🏁 Script executed:

# Examine the QAKFParticleTracktoCalo source files
cd offline/QA/KFParticle_Track2Calo
echo "=== QAKFParticleTracktoCalo.h ===" 
cat -n QAKFParticleTracktoCalo.h
echo ""
echo "=== QAKFParticleTracktoCalo.cc (first 150 lines) ===" 
head -150 QAKFParticleTracktoCalo.cc

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 16688


🏁 Script executed:

# Search for QAHistManagerDef
rg -n "QAHistManagerDef" offline --type h

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 522


🏁 Script executed:

# Check the KFParticle/Makefile.am sibling for comparison
cat -n offline/QA/KFParticle/Makefile.am

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 1346


Use LIBADD instead of LDFLAGS and add missing library dependencies.

Verified: This module uses LDFLAGS (incorrect for libtool targets), while all sibling QA modules correctly use LIBADD. Additionally, the source code includes headers from several libraries not currently linked:

  • KFParticle_Container, KFParticle_Tools-lkfparticle_sphenix (line 26–27 of header)
  • SvtxTrack, SvtxTrackMap-ltrack_io (lines 29–30)
  • ActsGeometry, TrkrCluster*, TrkrDefs-ltrackbase_historic (lines 31–37)
  • SvtxVertexMap, SvtxVertex-lglobalvertex_io (lines 22–23)
  • QAHistManagerDef-lqautils (qautils/QAHistManagerDef.h in source)
  • SubsysReco-lSubsysReco (line 4; not always transitively exported by -lfun4all)
  • -lphool is also needed (used throughout; included by peer modules)
Suggested rework
-libQAKFParticleTracktoCalo_la_LDFLAGS = \
-  -L$(libdir) \
-  -L$(OFFLINE_MAIN)/lib \
-  -lKFParticle \
-  -lcalo_io \
-  -lCLHEP \
-  -lfun4all
+libQAKFParticleTracktoCalo_la_LIBADD = \
+  -lKFParticle \
+  -lkfparticle_sphenix \
+  -lcalo_io \
+  -lCLHEP \
+  -lfun4all \
+  -lSubsysReco \
+  -lphool \
+  -lqautils \
+  -lglobalvertex_io \
+  -ltrack_io \
+  -ltrackbase_historic
📝 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
libQAKFParticleTracktoCalo_la_LDFLAGS = \
-L$(libdir) \
-L$(OFFLINE_MAIN)/lib \
-lKFParticle \
-lcalo_io \
-lCLHEP \
-lfun4all
libQAKFParticleTracktoCalo_la_LIBADD = \
-lKFParticle \
-lkfparticle_sphenix \
-lcalo_io \
-lCLHEP \
-lfun4all \
-lSubsysReco \
-lphool \
-lqautils \
-lglobalvertex_io \
-ltrack_io \
-ltrackbase_historic

Comment on lines +126 to +127
return -1;
}
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

Return a proper Fun4All return code from process_event.

return -1; is not a Fun4All return code and can produce confusing behavior in the scheduler (it is not ABORTEVENT/ABORTRUN/EVENT_OK). Given the PR description mentions that this module sometimes sees an empty KFParticle container when run alongside the general KFParticle QA, please also distinguish "container absent" from "container present but empty" — silently aborting when absent is fine, but an empty container per event is probably not fatal.

-  if (!m_kfpContainer){ 
-    std::cout << "KFP CONTAINER - Fatal Error - unable to find DST node KFParticle_QA" << std::endl;
-    emptyKFPcontainer++;
-    return -1;
-  }
-  else filledKFPcontainer++;
+  if (!m_kfpContainer) {
+    if (m_verbosity > 0)
+      std::cout << __PRETTY_FUNCTION__ << " : missing KFParticle_Container node " << m_KFParticleNodeName << "_KFParticle_Container" << std::endl;
+    emptyKFPcontainer++;
+    return Fun4AllReturnCodes::ABORTEVENT;
+  }
+  ++filledKFPcontainer;

Comment on lines +133 to +143
{ std::cout << __LINE__ << std::endl;
// Setup // std::vector<std::pair<SvtxTrack*,KFParticle*>> v_daughters={}; // Holds all daughters belonging to a (single) candidate mother
std::vector<QAKFParticleTracktoCalo::MatchedCluster*> v_matched_clusterstracks = {}; // Holds only duaghters with matches
SvtxVertex *m_vertex;

//Get mother
KFParticle *mother = iter.second;
if (!mother){
if (m_verbosity > 0) std::cout << __PRETTY_FUNCTION__ << " Error: null mother pointer" << std::endl;
continue;
} std::cout << __LINE__ << std::endl;
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

Remove leftover __LINE__ debug prints.

std::cout << __LINE__ << std::endl; in the hot per-event loop will produce one or two lines per KFParticle mother per event. In a QA job over many events this floods logs and materially slows the module. Please drop (or gate on m_verbosity).

-  for (auto &iter : *m_kfpContainer)
-  { std::cout << __LINE__ << std::endl;
+  for (auto &iter : *m_kfpContainer)
+  {
     ...
-    } std::cout << __LINE__ << std::endl;
+    }
📝 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
{ std::cout << __LINE__ << std::endl;
// Setup // std::vector<std::pair<SvtxTrack*,KFParticle*>> v_daughters={}; // Holds all daughters belonging to a (single) candidate mother
std::vector<QAKFParticleTracktoCalo::MatchedCluster*> v_matched_clusterstracks = {}; // Holds only duaghters with matches
SvtxVertex *m_vertex;
//Get mother
KFParticle *mother = iter.second;
if (!mother){
if (m_verbosity > 0) std::cout << __PRETTY_FUNCTION__ << " Error: null mother pointer" << std::endl;
continue;
} std::cout << __LINE__ << std::endl;
{
// Setup // std::vector<std::pair<SvtxTrack*,KFParticle*>> v_daughters={}; // Holds all daughters belonging to a (single) candidate mother
std::vector<QAKFParticleTracktoCalo::MatchedCluster*> v_matched_clusterstracks = {}; // Holds only duaghters with matches
SvtxVertex *m_vertex;
//Get mother
KFParticle *mother = iter.second;
if (!mother){
if (m_verbosity > 0) std::cout << __PRETTY_FUNCTION__ << " Error: null mother pointer" << std::endl;
continue;
}

Comment on lines +162 to +170
std::pair<float,float> anglesbetweendaughters = {std::numeric_limits<float>::quiet_NaN(),std::numeric_limits<float>::quiet_NaN()};
if(m_cut_on_angle_between_tracks){
anglesbetweendaughters = GetEtaAndPhiBetweenDaughters(v_daughters);
if(!GoodAngleBetweenDaughters(anglesbetweendaughters)){
failedcut_dAngle++;
std::cout << "Bad dAngle: " << anglesbetweendaughters.first << " " << anglesbetweendaughters.second << std::endl;
continue;
}
}
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

NaN silently passes the daughter-angle cut.

When v_daughters.size() != 2 or the daughters have the same-sign charge, GetEtaAndPhiBetweenDaughters returns {NaN, NaN}. In GoodAngleBetweenDaughters, std::abs(NaN) > threshold is false, so NaN is treated as passing the cut. That means same-sign or non-two-body candidates slip through m_cut_on_angle_between_tracks untouched, and the filled h_dEta_tracks/h_dPhi_tracks entries on Lines 441-442 will receive NaNs.

-  if((float) std::abs(dEta_tracks) > m_dEta_Tracks) {return false;}
-  if((float) std::abs(dPhi_tracks) > m_dPhi_Tracks) {return false;}
+  if (!std::isfinite(dEta_tracks) || !std::isfinite(dPhi_tracks)) return false;
+  if (std::abs(dEta_tracks) > m_dEta_Tracks) return false;
+  if (std::abs(dPhi_tracks) > m_dPhi_Tracks) return false;

Comment on lines +193 to +194
// Get vertex
m_vertex = m_vertexMap->get(v_daughters[0].first->get_vertex_id());
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

Potential null-pointer dereference on m_vertexMap.

LoadNodes only prints an error when SvtxVertexMap is missing and leaves m_vertexMap == nullptr. This line then calls m_vertexMap->get(...) unconditionally, which will crash. Either bail out of process_event when the vertex map (or any other mandatory node) is missing, or guard the call here. The commented-out return Fun4AllReturnCodes::ABORTRUN; paths in LoadNodes suggest the original intent.

   // Get vertex
+  if (!m_vertexMap) { continue; }
   m_vertex = m_vertexMap->get(v_daughters[0].first->get_vertex_id());

A cleaner fix is to return ABORTEVENT from process_event when required containers are missing, rather than silently continuing with half-loaded state (which may be related to the crash described in the PR description when running alongside the general KFParticle QA).

📝 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
// Get vertex
m_vertex = m_vertexMap->get(v_daughters[0].first->get_vertex_id());
// Get vertex
if (!m_vertexMap) { continue; }
m_vertex = m_vertexMap->get(v_daughters[0].first->get_vertex_id());

if(MaCl->track_charge<0){
// std::cout << "Filling histos for electrons..." << std::endl;
pt_m = MaCl->track_pt_svtx;
h_pt_m->Fill(pt_p);
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

Wrong variable fills negative-daughter pT histogram.

h_pt_m is filled with pt_p instead of pt_m, so the "minus" pT spectrum is a copy of the "plus" spectrum — a silent QA bug that will quietly corrupt every conversion study using this plot.

-      h_pt_m->Fill(pt_p);
+      h_pt_m->Fill(pt_m);
📝 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
h_pt_m->Fill(pt_p);
h_pt_m->Fill(pt_m);

Comment on lines +487 to +626
QAKFParticleTracktoCalo::MatchedCluster* QAKFParticleTracktoCalo::GetMatchedCluster(SvtxTrackState *thisState, SvtxVertex *m_vertex){

// Objects
MatchedCluster* MaCl = new MatchedCluster;
RawCluster *cluster;
bool is_match;
int index;

// Make sure there's a state
if (thisState == nullptr || m_vertex==nullptr) return MaCl;

// Vertex
float vx = m_vertex->get_x();
float vy = m_vertex->get_y();
float vz = m_vertex->get_z();

// Tracking variables
float _track_phi_emc = std::numeric_limits<float>::quiet_NaN();
float _track_eta_emc = std::numeric_limits<float>::quiet_NaN();
float _track_x_emc = std::numeric_limits<float>::quiet_NaN();
float _track_y_emc = std::numeric_limits<float>::quiet_NaN();
float _track_z_emc = std::numeric_limits<float>::quiet_NaN();

// EMCal vectors
std::vector<float> v_emcal_phi;
std::vector<float> v_emcal_eta;
std::vector<float> v_emcal_clusE;
std::vector<float> v_emcal_dphi;
std::vector<float> v_emcal_deta;
std::vector<float> v_emcal_dr;
std::vector<float> v_emcal_dz;

//Track state kinematics
_track_x_emc = thisState->get_x()-vx;
_track_y_emc = thisState->get_y()-vy;
_track_z_emc = thisState->get_z()-vz;
_track_phi_emc = std::atan2(_track_y_emc, _track_x_emc);
_track_eta_emc = std::asinh(_track_z_emc / std::sqrt((_track_x_emc * _track_x_emc) + (_track_y_emc * _track_y_emc)));

// Set variables for matching
is_match = false;
index = -1;
// Create objects, containers, iterators for clusters
cluster = nullptr;
RawClusterContainer::Range begin_end_EMC = clustersEM->getClusters();
RawClusterContainer::Iterator clusIter_EMC;

// Loop over the EMCal clusters
for (clusIter_EMC = begin_end_EMC.first; clusIter_EMC != begin_end_EMC.second; ++clusIter_EMC)
{
//Get cluster with respect to PV, not (0,0,0)
cluster = clusIter_EMC->second;
CLHEP::Hep3Vector v_vertex;
v_vertex.set(vx,vy,vz);
CLHEP::Hep3Vector E_vec_cluster = RawClusterUtility::GetEVec(*cluster, v_vertex);

bool skipthis = false;

//CUT -- CLUSTER ENERGY
float cluster_energy = E_vec_cluster.mag();
if (cluster_energy < m_emcal_e_low_cut){
skipthis=true; failedcut_energy++;
std::cout << "Bad energy: " << cluster_energy << std::endl;
} //continue;}

//CUT -- DPHI
float clPhi = E_vec_cluster.phi();
float dphi = PiRange(_track_phi_emc - clPhi);
if (dphi > m_dphi_cut_high || dphi < m_dphi_cut_low){
skipthis=true; failedcut_dPhi++;
std::cout << "Bad dPhi: " << dphi << std::endl;
} // continue;}

//CUT -- DZ
float clZ = cluster->get_z() - vz;
float dz = _track_z_emc - clZ;
if (dz > m_dz_cut_high || dz < m_dz_cut_low){
skipthis=true; failedcut_dZ++;
std::cout << "Bad dZ: " << dz << std::endl;
}// continue;}

// CUT -- DETA // Currently not being used, but want to include functionality now
float clEta = E_vec_cluster.pseudoRapidity();
float deta = _track_eta_emc - clEta;
// if (deta > m_deta_cut_high || deta < m_deta_cut_low) continue;

if(skipthis) continue;

//Calculate dr
float tmparg = caloRadiusEMCal * dphi;
float dr = std::sqrt((tmparg * tmparg) + (dz * dz)); // sqrt((R*dphi)^2 + (dz)^2
// float dr = sqrt((dphi*dphi + deta*deta)); //previous version

// Add potential match's information to vectors
v_emcal_phi.push_back(clPhi);
v_emcal_eta.push_back(clEta);
v_emcal_clusE.push_back(cluster_energy);
v_emcal_dphi.push_back(dphi);
v_emcal_dz.push_back(dz);
v_emcal_deta.push_back(deta);
v_emcal_dr.push_back(dr);
is_match = true;
number_of_matches++;
}

// Find the closest match from all potential matches
if (is_match == true)
{
float tmp = 99999;
for (long unsigned int i = 0; i < v_emcal_dr.size(); i++)
{
if (v_emcal_dr[i] < tmp)
{
index = i;
tmp = v_emcal_dr[i];
}
}
}

if (is_match == false)
{
return MaCl;
}
else
{
MaCl->track_phi_emc=_track_phi_emc;
MaCl->track_eta_emc=_track_eta_emc;
MaCl->track_x_emc=_track_x_emc;
MaCl->track_y_emc=_track_y_emc;
MaCl->track_z_emc=_track_z_emc;
MaCl->cluster_phi=v_emcal_phi[index];
MaCl->cluster_eta=v_emcal_eta[index];
MaCl->cluster_E=v_emcal_clusE[index];
MaCl->dphi_trackcluster=v_emcal_dphi[index];
MaCl->deta_trackcluster=v_emcal_deta[index];
MaCl->dz_trackcluster=v_emcal_dz[index];
MaCl->matched=true;
return MaCl;
}
}
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

MatchedCluster leaks on every call; use a value (or unique_ptr) instead of raw new.

GetMatchedCluster unconditionally news a MatchedCluster and returns the raw pointer. The callers at Lines 210–214 and 228–230 never delete it: unmatched candidates are dropped on the floor, and matched ones are pushed into v_matched_clusterstracks and also never freed at the end of process_event. Every event leaks one heap allocation per EMCal-matching attempt, which in a long QA run is noticeable and, more importantly, unnecessary — MatchedCluster is a trivially copyable POD and doesn't need heap allocation at all.

🛠️ Suggested shape

Prefer returning by value (and store by value in the vector):

MatchedCluster QAKFParticleTracktoCalo::GetMatchedCluster(SvtxTrackState* thisState, SvtxVertex* m_vertex)
{
  MatchedCluster MaCl;       // matched=false by default
  if (!thisState || !m_vertex) return MaCl;
  ...
  return MaCl;               // NRVO
}

// caller:
std::vector<MatchedCluster> v_matched_clusterstracks;
auto matched = GetMatchedCluster(thisState, m_vertex);
if (!matched.matched) { failedcut_nomtch++; continue; }
...
v_matched_clusterstracks.push_back(matched);

If you want to keep pointer semantics, at minimum use std::unique_ptr<MatchedCluster> and a std::vector<std::unique_ptr<MatchedCluster>>, or ensure all allocations are deleted before process_event returns.

Comment on lines +899 to +901
m_outputpath = path;
m_outtrailer = "_" + m_runnumber + ext;
std::string file = m_outputpath + filename + m_outtrailer;
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

Undefined behavior in m_outtrailer construction.

"_" + m_runnumber + ext does not do what it looks like. "_" is a const char[2] that decays to const char*; const char* + int is pointer arithmetic, not string concatenation. For any run number ≥ 2 you walk past the end of the string literal — undefined behavior, and in practice you will get a garbage trailer (or crash) appended before ext. The header also declares m_runnumber = std::numeric_limits<int>::quiet_NaN(), which for int is just 0, so this hasn't tripped in trivial tests but will in production.

-  m_outtrailer = "_" + m_runnumber + ext;
+  m_outtrailer = "_" + std::to_string(m_runnumber) + ext;

And in the header:

-  int m_runnumber = std::numeric_limits<int>::quiet_NaN();
+  int m_runnumber{0};

Comment on lines +74 to +79
void setTrackClusterDeltaZCut(float in){ m_dz_cut_low = in; m_dz_cut_high = in;}
void setTrackClusterDeltaZMin(float in){ m_dz_cut_high = in;}
void setTrackClusterDeltaZMax(float in){ m_dz_cut_low = in;}
void setTrackClusterDeltaPhiCut(float in){ m_dphi_cut_low = in; m_dphi_cut_high = in;}
void setTrackClusterDeltaPhiMin(float in){ m_dphi_cut_high = in;}
void setTrackClusterDeltaPhiMax(float in){ m_dphi_cut_low = in;}
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

Broken public API: cut setters are swapped and the "Cut" variants are not symmetric.

These setters will silently mis-configure users' cuts:

  • setTrackClusterDeltaZCut(in) sets both m_dz_cut_low = in and m_dz_cut_high = in, so the cut at process_event (dz > high || dz < low) only accepts dz == in. Presumably meant a symmetric cut (-in, +in).
  • setTrackClusterDeltaZMin(in) writes into m_dz_cut_high — i.e. the max member.
  • setTrackClusterDeltaZMax(in) writes into m_dz_cut_low — i.e. the min member.
  • The same three problems exist for the three ...DeltaPhi... setters.

Anyone calling these in a macro will get the opposite or an empty window.

-  void setTrackClusterDeltaZCut(float in){ m_dz_cut_low = in; m_dz_cut_high = in;}
-  void setTrackClusterDeltaZMin(float in){ m_dz_cut_high = in;}
-  void setTrackClusterDeltaZMax(float in){ m_dz_cut_low = in;}
-  void setTrackClusterDeltaPhiCut(float in){ m_dphi_cut_low = in; m_dphi_cut_high = in;}
-  void setTrackClusterDeltaPhiMin(float in){ m_dphi_cut_high = in;}
-  void setTrackClusterDeltaPhiMax(float in){ m_dphi_cut_low = in;}
+  // Symmetric window: |dz| <= in
+  void setTrackClusterDeltaZCut(float in)     { m_dz_cut_low = -std::abs(in); m_dz_cut_high = std::abs(in); }
+  void setTrackClusterDeltaZMin(float in)     { m_dz_cut_low  = in; }
+  void setTrackClusterDeltaZMax(float in)     { m_dz_cut_high = in; }
+  void setTrackClusterDeltaPhiCut(float in)   { m_dphi_cut_low = -std::abs(in); m_dphi_cut_high = std::abs(in); }
+  void setTrackClusterDeltaPhiMin(float in)   { m_dphi_cut_low  = in; }
+  void setTrackClusterDeltaPhiMax(float in)   { m_dphi_cut_high = in; }
📝 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
void setTrackClusterDeltaZCut(float in){ m_dz_cut_low = in; m_dz_cut_high = in;}
void setTrackClusterDeltaZMin(float in){ m_dz_cut_high = in;}
void setTrackClusterDeltaZMax(float in){ m_dz_cut_low = in;}
void setTrackClusterDeltaPhiCut(float in){ m_dphi_cut_low = in; m_dphi_cut_high = in;}
void setTrackClusterDeltaPhiMin(float in){ m_dphi_cut_high = in;}
void setTrackClusterDeltaPhiMax(float in){ m_dphi_cut_low = in;}
// Symmetric window: |dz| <= in
void setTrackClusterDeltaZCut(float in) { m_dz_cut_low = -std::abs(in); m_dz_cut_high = std::abs(in); }
void setTrackClusterDeltaZMin(float in) { m_dz_cut_low = in; }
void setTrackClusterDeltaZMax(float in) { m_dz_cut_high = in; }
void setTrackClusterDeltaPhiCut(float in) { m_dphi_cut_low = -std::abs(in); m_dphi_cut_high = std::abs(in); }
void setTrackClusterDeltaPhiMin(float in) { m_dphi_cut_low = in; }
void setTrackClusterDeltaPhiMax(float in) { m_dphi_cut_high = in; }

Comment on lines +213 to +214
int m_runnumber = std::numeric_limits<int>::quiet_NaN();
void runnumber(const int run) { m_runnumber = run; }
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

quiet_NaN() is meaningless for int.

std::numeric_limits<int>::quiet_NaN() returns 0 (integers have no NaN representation), so the "sentinel" is just zero — indistinguishable from a legitimate run number. Combined with the bogus concatenation in GetOutputFileNameAndPath (flagged separately), this is how you silently end up with wrong output filenames.

Also: runnumber(int) is a public configuration entry point but lives in the public: verbosity block without a set prefix; consider setRunNumber for API consistency with the rest of the setters.

-  int m_runnumber = std::numeric_limits<int>::quiet_NaN();
-  void runnumber(const int run) { m_runnumber = run; }
+  int m_runnumber{0};
+  void setRunNumber(const int run) { m_runnumber = run; }
📝 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 m_runnumber = std::numeric_limits<int>::quiet_NaN();
void runnumber(const int run) { m_runnumber = run; }
int m_runnumber{0};
void setRunNumber(const int run) { m_runnumber = run; }

@sphenix-jenkins-ci
Copy link
Copy Markdown

Build & test report

Report for commit cdc4af2acdb9bf97016b82d889b534ae958405ce:
Jenkins on fire


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

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.

1 participant