Skip to content

Test2#13

Open
blackcathj wants to merge 214 commits intosPHENIX-Test:masterfrom
pjwinnetou:master
Open

Test2#13
blackcathj wants to merge 214 commits intosPHENIX-Test:masterfrom
pjwinnetou:master

Conversation

@blackcathj
Copy link

@blackcathj blackcathj commented Jan 19, 2026

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 by CodeRabbit

  • New Features

    • Configurable particle-based trigger; jet/particle gating (pdg/eta/pT)
    • State-cluster residuals QA module; new tracking QA utilities
    • Pre-file-opening script hook for custom preprocessing
    • dE/dx global fitter + test tools; functional calorimeter waveform fitter
    • Combine-tower merging utility for embedding
  • Improvements

    • Calorimeter: separate EM/HCAL thresholds and species-aware gating
    • Micromegas: sample-range gating and expanded QA fields
    • Skimmer: optional max-accepted-events guard
  • Bug Fixes

    • Preserve/restored cout formatting to avoid output corruption

Daniel J Lis and others added 30 commits December 17, 2025 20:59
Fill ntuple for both matched and unmatched waveforms
…s_pool_evaluation_update

Small update on TPOT event combining evaluation
if multiple hits are found on the same strip only the first one, timewise, is kept.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
add quality flag to total energy calc and correlations
Steepspace and others added 29 commits January 28, 2026 15:52
- Updated downscale factors for the calorimeters and mbd by the ratio of nucleons: OO / AuAu
- Add "OO" as a m_species option
- Updated RunnumberRange to place temporary first and last for OO as well as update the last runnumber for proton+proton.
added mbd_status calib, overlapping waveforms (pileup) fit
Docstrings generation was requested by @pinkenburg.

* sPHENIX-Collaboration#4145 (comment)

The following files were modified:

* `offline/QA/Calorimeters/CaloValid.cc`
* `offline/framework/phool/RunnumberRange.h`
…ion/coderabbitai/docstrings/373f714

📝 Add docstrings to `CaloValid-OO-update`
…_fix

Use short int for vertex beam crossing
…transforms

Minor updates to PHActsSiliconSeeder, TrackResiduals, PHSimpleVertexFinder
does not try to download sampmax calibs etc for sims
Improvements to Cluster State Residuals QA
…izer_fix

Make TrkrNtuplizer do_hit_eval do hit eval (and also fix PID)
Copy link

@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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
offline/packages/trackreco/PHSiliconSeedMerger.cc (3)

98-98: ⚠️ Potential issue | 🔴 Critical

std::numeric_limits<int>::quiet_NaN() is invalid for integer types.

quiet_NaN() is only meaningful for floating-point types. For integers, this returns 0 (or is undefined behavior in some implementations). The comparison track1Strobe == track2Strobe at line 188/202 will incorrectly match when both are uninitialized.

Consider using a sentinel value like -1 or std::optional<int> to represent "not set".

🐛 Proposed fix using sentinel value
-    int track1Strobe = std::numeric_limits<int>::quiet_NaN();
+    int track1Strobe = -1;  // sentinel for "no MVTX cluster found"

And similarly for line 134:

-      int track2Strobe = std::numeric_limits<int>::quiet_NaN();
+      int track2Strobe = -1;  // sentinel for "no MVTX cluster found"

Then update the comparison logic to check for valid strobes:

-          if (track1Strobe == track2Strobe && m_mergeSeeds)
+          if (track1Strobe >= 0 && track1Strobe == track2Strobe && m_mergeSeeds)

134-134: ⚠️ Potential issue | 🔴 Critical

Same quiet_NaN() issue for track2Strobe.

See comment above regarding line 98. This also needs to be fixed.


183-225: ⚠️ Potential issue | 🔴 Critical

Logic error: keysToKeep is computed but never used, and duplicate insertions occur.

There are two issues in this block:

  1. keysToKeep (lines 184, 187-191, 201, 203-205) accumulates the merged keys but is never used afterward. The intended merge is not being applied.

  2. Lines 223-225 unconditionally insert track1ID into matches and track2ID into seedsToDelete, duplicating the logic already handled in the if/else branches above (lines 192-193, 206-207). This causes:

    • When mvtx1Keys.size() >= mvtx2Keys.size(): correct behavior (track1ID kept, track2ID deleted)
    • When mvtx2Keys.size() > mvtx1Keys.size(): wrong behavior - track2ID should be kept but lines 223-225 override to keep track1ID and delete track2ID
🐛 Proposed fix to remove duplicate insertions and use keysToKeep
         if (mvtx1Keys.size() >= mvtx2Keys.size())
         {
           keysToKeep = mvtx1Keys;
           if (track1Strobe == track2Strobe && m_mergeSeeds)
           {
             keysToKeep.insert(mvtx2Keys.begin(), mvtx2Keys.end());
           }
           matches.insert(std::make_pair(track1ID, keysToKeep));
           seedsToDelete.insert(track2ID);
           if (Verbosity() > 2)
           {
             std::cout << "     will delete seed " << track2ID << std::endl;
           }
         }
         else
         {
           keysToKeep = mvtx2Keys;
           if (track1Strobe == track2Strobe && m_mergeSeeds)
           {
             keysToKeep.insert(mvtx1Keys.begin(), mvtx1Keys.end());
           }
           matches.insert(std::make_pair(track2ID, keysToKeep));
           seedsToDelete.insert(track1ID);
           if (Verbosity() > 2)
           {
             std::cout << "     will delete seed " << track1ID << std::endl;
           }
         }

         if (Verbosity() > 2)
         {
           std::cout << "Match IDed" << std::endl;
-          for (const auto& key : mvtx1Keys)
+          for (const auto& key : keysToKeep)
           {
             std::cout << "  total track keys " << key << std::endl;
           }
         }

-        matches.insert(std::make_pair(track1ID, mvtx1Keys));
-        seedsToDelete.insert(track2ID);
         break;
       }
offline/packages/trackreco/PHSimpleVertexFinder.cc (2)

282-286: ⚠️ Potential issue | 🟠 Major

Potential null pointer dereference on siseed.

At line 286, siseed->get_crossing() is called without a null check. While track is verified non-null at line 283, get_silicon_seed() at line 285 can return nullptr, leading to a crash.

🐛 Proposed fix
 	  SvtxTrack *track = _track_map->get(trid);
 	  if (track)
 	    {
 	      auto *siseed = track->get_silicon_seed();
+	      if (!siseed)
+	        {
+	          continue;
+	        }
 	      short int intt_crossing = siseed->get_crossing();

399-400: ⚠️ Potential issue | 🟠 Major

Potential null pointer dereference on siseed in verbose output loop.

Same issue as above: siseed->get_crossing() at line 400 is called without checking if siseed is null.

🐛 Proposed fix
         auto *siseed = track->get_silicon_seed();
+        if (!siseed)
+        {
+          continue;
+        }
         short int intt_crossing = siseed->get_crossing();
🤖 Fix all issues with AI agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h`:
- Line 88: The default value for the member _thePtLow is inconsistent: the
header HepMCParticleTrigger.h sets float _thePtLow{-999.9} while the constructor
initializer in the .cc sets it to 0; align them by choosing one canonical
default and making both places match (preferably set the header and the
constructor initializer list for HepMCParticleTrigger to the same value, e.g.,
-999.9 or 0) so the declaration of _thePtLow and the constructor initializer
list in the HepMCParticleTrigger class are identical.

In `@offline/packages/globalvertex/GlobalVertexv3.h`:
- Around line 22-26: CloneMe currently returns a shallow copy via the default
copy constructor which duplicates raw pointers in _vtxs and leads to
double-free/UAF when Reset()/destructor run; change CloneMe in class
GlobalVertexv3 to perform a deep clone by creating a new GlobalVertexv3 instance
and for each pointer in the original's _vtxs allocate/clone a fresh vertex
object (using the vertex type's copy constructor or its Clone/CloneMe method if
available) and push those new pointers into the new object's _vtxs, ensuring
ownership semantics match Reset()/destructor and avoiding shared raw pointers
between original and clone.

In `@offline/packages/globalvertex/SvtxVertex_v2.h`:
- Around line 61-71: set_beam_crossing currently casts rollover_short(cross) to
unsigned which maps negative values (e.g. -32768) to large unsigned sentinel
values; change set_beam_crossing to validate the result of rollover_short(cross)
before casting: call rollover_short(cross) into a short int (cross_ro), and if
cross_ro is negative set _beamcrossing to a defined safe value (e.g. 0u or
another agreed clamp) instead of static_cast<unsigned int>(cross_ro); keep the
existing special-case when cross == short_int_max that sets _beamcrossing to
std::numeric_limits<unsigned int>::max(), and update any callers/contract if a
different sentinel is chosen.
- Around line 91-98: rollover_short currently maps cross == -32768 into the same
sentinel slot as short_int_max causing collisions; update rollover_short to
explicitly handle the minimum short value (use numeric_limits<short>::min() or
the defined short_int_min) and either (a) reject it (throw std::out_of_range or
assert) so set_beam_crossing can reject/document out-of-range inputs, or (b) map
it to a distinct reserved value (not short_int_max) so serialization is
bijective; ensure callers set_beam_crossing/get_beam_crossing handle the new
behavior and update any docs/comments accordingly.

In `@offline/packages/globalvertex/SvtxVertex_v3.cc`:
- Around line 106-112: The covar_index method lacks bounds checking and can
produce out-of-range indexes into the _err[6] covariance storage; update
SvtxVertex_v3::covar_index to validate that both i and j are within [0,2] before
computing the packed index (keep the existing std::swap(i,j) behavior), and if
an index is invalid, fail fast by throwing a std::out_of_range (or using an
assert) with a clear message mentioning the invalid i/j values and the object,
so callers cannot index _err out-of-bounds.

In `@offline/packages/jetbackground/DetermineTowerBackground.cc`:
- Around line 542-602: The code in DetermineTowerBackground::process_event
currently uses a literal return -1 when G4TruthInfo is missing and exit(-1) when
EventplaneinfoMap is missing; replace both with the framework-safe return value
Fun4AllReturnCodes::ABORTRUN so the framework can handle the abort: change the
early return after the truthinfo null check (where it currently does return -1)
to return Fun4AllReturnCodes::ABORTRUN, and replace the exit(-1) in the
EventplaneinfoMap null check with return Fun4AllReturnCodes::ABORTRUN (ensure
the correct header/namespace for Fun4AllReturnCodes is included where
DetermineTowerBackground::process_event is defined).
- Around line 970-976: The range check for centrality_bin in
DetermineTowerBackground.cc wrongly excludes valid indices (0 and 95–99); update
the conditional in the block that assigns _v2 (inside the code path using
centinfo->get_centrality_bin) to accept the full populated range—use
centrality_bin >= 0 && centrality_bin < 100 or better yet centrality_bin >= 0 &&
centrality_bin < static_cast<int>(_CENTRALITY_V2.size())—so _v2 is set from
_CENTRALITY_V2[centrality_bin] for valid bins and avoid incorrectly marking flow
failure for those bins.

In `@offline/packages/jetbase/TowerJetInput.cc`:
- Line 13: Remove the unused include directive for GlobalVertexv3.h: locate the
top-of-file include block where `#include` <globalvertex/GlobalVertexv3.h> is
present and delete that line; the file only uses GlobalVertexMap and calls
GlobalVertex::get_z(), so no references to GlobalVertexv3 exist and removing the
include will clean up the headers.

In `@offline/packages/mbd/MbdCalib.cc`:
- Around line 89-99: Download_Status currently treats a missing status file/URL
as an error and leaves the member _mbdstatus at -1, causing Download_All and
repeated runs to fail or retain stale values; change the logic so that a missing
status is non-fatal and defaults to "good" by setting _mbdstatus = 0 (either in
the MbdCalib constructor/initializer or at start of Download_Status), treat a
not-found / empty response as success (no error) while still parsing the content
and returning errors for malformed content, and update any checks in
Download_All that assume a non-negative _mbdstatus so they do not fail when the
status was simply absent; key symbols to modify: Download_Status, Download_All,
and the _mbdstatus initialization in the MbdCalib class.
- Around line 1421-1427: Download_TimeRMS currently returns distinct codes (1
success, 0 missing, negative errors) but callers ignore its return so they
cannot distinguish missing TimeRMS from success; update every caller that
invokes Download_TimeRMS to capture its return value, check for ==1 (proceed),
==0 (log a clear warning that _trms_y was empty and handle missing calibration
path), and <0 (treat as error: log with the returned code and propagate or fail
as appropriate). Use get_trms() only for safe access to _trms_y_interp but do
not rely on it to infer load status; if desired, convert non-1 results into
appropriate control flow (early return, exception, or status propagation) so
callers can debug and respond to missing or error states.

In `@offline/packages/TrackingDiagnostics/TrkrNtuplizer.cc`:
- Around line 1542-1549: The range check for INTT layers incorrectly uses
"layer_local < _nlayers_intt", causing most INTT layers to be skipped; update
the condition in the block that handles INTT hits (where variables like
layer_local, _nlayers_maps, _nlayers_intt, InttDefs, and CylinderGeomIntt
appear) so the upper bound is _nlayers_maps + _nlayers_intt (i.e., use
layer_local >= _nlayers_maps && layer_local < _nlayers_maps + _nlayers_intt) to
correctly detect all INTT layers before calling find_strip_center_localcoords.

In `@offline/packages/trackreco/PHSiliconSeedMerger.cc`:
- Line 92: The build fails because code (e.g., PHSiliconSeedMerger.cc using
seedsToDelete.contains(track1ID)) relies on C++20; update the build
configuration to enable C++20 by modifying configure.ac to add the C++20
standard flag to the compiler flags (either append -std=c++20 to
CXXFLAGS/CXXFLAGS_DEFAULT) or, preferably, use Autoconf’s AX_CHECK_COMPILE_FLAG
(or similar) to detect and add a supported C++20 flag; ensure this change covers
all trackreco sources that call std::set::contains and that the configure check
emits a clear error if no C++20-supporting flag is available.

In `@offline/QA/Tracking/StateClusterResidualsQA.cc`:
- Around line 114-142: The loop that pushes dynamic_cast results from
hm->getHisto into vectors (e.g., m_histograms_x, m_histograms_y,
m_histograms_layer_x, etc.) can store null pointers if getHisto() returns
nullptr or the cast fails, leading to crashes later in process_event when Fill()
is called; update the loop that iterates over m_pending (inside the
m_use_local_coords branches) to check each returned pointer before push_back (or
assert/throw with a clear message), and only push valid TH1/TH2 pointers (or
substitute a safe no-op placeholder) so that subsequent process_event accesses
of those vectors are guaranteed non-null. Ensure checks reference the same calls
shown (hm->getHisto(std::string(cfg.name + "...")) and the dynamic_cast results)
and adjust downstream logic in process_event to assume non-null or handle the
placeholder consistently.
- Around line 184-262: The loop over m_pending uses a separate counter h that is
skipped when early continue is taken (in the charge filter), causing histogram
indexing to desynchronize; fix by ensuring h is incremented before every
continue in the for (const auto& cfg : m_pending) loop (i.e., place ++h just
before each continue in the charge checks) or refactor the loop to an
index-based iteration (for (size_t h=0; h<m_pending.size(); ++h) using
m_pending[h]) so the histogram index always matches the config; update
references to cfg accordingly (m_pending, h, charge filter block, and the
histogram-fill section).

In `@offline/QA/Tracking/StateClusterResidualsQA.h`:
- Line 181: The header guard closing comment is incorrect: the guard used is
STATECLUSTERRESIDUALSQA_H but the closing comment reads TRACKFITTINGQA_H; update
the trailing comment to match the guard identifier (STATECLUSTERRESIDUALSQA_H)
so the opening and closing guard identifiers are consistent with the
`#ifndef/`#define for STATECLUSTERRESIDUALSQA_H.
🧹 Nitpick comments (26)
simulation/g4simulation/g4main/PHG4TruthTrackingAction.h (1)

22-83: Attach the Doxygen blocks directly to the declarations they document.
These comment blocks sit outside the class and may be treated as orphaned by doc tooling; move them adjacent to each member (or consolidate into class-level docs) so they’re reliably picked up.

Example of moving one block (apply the same pattern to others)
-class PHG4TruthTrackingAction : public PHG4TrackingAction
-{
- public:
-  //! constructor
-  PHG4TruthTrackingAction(PHG4TruthEventAction*);
+class PHG4TruthTrackingAction : public PHG4TrackingAction
+{
+ public:
+  /**
+   * Construct a PHG4TruthTrackingAction associated with an event action.
+   * `@param` eventAction Pointer to the owning PHG4TruthEventAction used to record per-event truth information.
+   */
+  PHG4TruthTrackingAction(PHG4TruthEventAction*);
offline/packages/trackreco/PHSiliconSeedMerger.h (3)

28-50: Inconsistent indentation for method definitions.

The method bodies (void trackMapName(...), void clusterOverlap(...), void searchIntt(), void mergeSeeds()) start at column 0 but should be indented to match the class member indentation level. This appears to be a formatting issue where the docblocks are indented but the method signatures are not.

🔧 Suggested indentation fix
   /**
  * Set the name of the track seed container to use when retrieving silicon tracks.
  *
  * `@param` name Name of the TrackSeedContainer node (defaults to "SiliconTrackSeedContainer").
  */
-void trackMapName(const std::string &name) { m_trackMapName = name; }
+  void trackMapName(const std::string &name) { m_trackMapName = name; }
   /**
  * Set the maximum number of overlapping clusters considered during seed merging.
  * `@param` nclusters Maximum number of clusters that may overlap (overlap threshold).
  */
-void clusterOverlap(const unsigned int nclusters) { m_clusterOverlap = nclusters; }
+  void clusterOverlap(const unsigned int nclusters) { m_clusterOverlap = nclusters; }
   /**
  * `@brief` Allow merging searches to include the INTT detector.
  *
  * Configure the merger to include INTT clusters in subsequent processing by disabling the MVTX-only restriction.
  */
-void searchIntt() { m_mvtxOnly = false; }
+  void searchIntt() { m_mvtxOnly = false; }
   /**
  * Enable merging of silicon seed tracks during event processing.
  *
  * When enabled, the module will merge overlapping silicon seed tracks where applicable.
  */
-void mergeSeeds() { m_mergeSeeds = true; }
+  void mergeSeeds() { m_mergeSeeds = true; }

57-73: Same indentation issue for member variable declarations.

The member variables and their documentation blocks have inconsistent indentation - unsigned int m_clusterOverlap{1}; and bool m_mvtxOnly{false}; are at column 0 instead of being indented within the class.

🔧 Suggested indentation fix
   /**
  * Minimum number of clusters that must be shared between two silicon track seeds
  * for them to be considered overlapping.
  *
  * Defaults to 1.
  */
-unsigned int m_clusterOverlap{1};
+  unsigned int m_clusterOverlap{1};
   bool m_mergeSeeds{false};
   /**
  * Restrict seed processing to the MVTX detector only.
  *
  * When set to `true`, operations that iterate or merge silicon seed tracks
  * will be limited to clusters originating from the MVTX vertex detector.
  * When `false`, clusters from other silicon detectors are included.
  */
-bool m_mvtxOnly{false};
+  bool m_mvtxOnly{false};
+  bool m_mergeSeeds{false};

75-77: Extra blank lines at end of file.

There are unnecessary trailing blank lines after the #endif guard. Consider removing them for cleaner formatting.

offline/packages/jetbackground/DetermineTowerBackground.cc (1)

79-106: Use RAII for CDBTTree to avoid manual delete paths.
[RAII + exception safety]

♻️ Suggested change
-  CDBTTree *cdbtree_calo_v2 = nullptr;
+  std::unique_ptr<CDBTTree> cdbtree_calo_v2;

...
-  cdbtree_calo_v2 = new CDBTTree(calibdir);
+  cdbtree_calo_v2 = std::make_unique<CDBTTree>(calibdir);

...
-  cdbtree_calo_v2->LoadCalibrations();
+  cdbtree_calo_v2->LoadCalibrations();

...
-  _CENTRALITY_V2[icent] = cdbtree_calo_v2->GetFloatValue(icent, "jet_calo_v2");
+  _CENTRALITY_V2[icent] = cdbtree_calo_v2->GetFloatValue(icent, "jet_calo_v2");
-
-  delete cdbtree_calo_v2;
offline/packages/globalvertex/SvtxVertex_v2.h (1)

100-118: Add documentation explaining the rollover conversion logic.

The conversion between unsigned int and short int involves complex rollover semantics that are not immediately obvious. Consider adding comments explaining:

  • The expected value ranges for _beamcrossing
  • Why values > 32767 might exist (legacy data, serialization compatibility)
  • The intended behavior of the double rollover at lines 114-117
simulation/g4simulation/g4main/PHG4TruthTrackingAction.cc (2)

360-427: Ancestry traversal lacks cycle detection—relies on Geant4 invariant.

The while (parent) loop at line 402 relies on the assumption that Geant4 always processes parents before children, ensuring no cycles. The comment at line 401 acknowledges this risk. While this invariant should hold in practice, a defensive check could prevent an infinite loop if the truth container is ever corrupted.

🛡️ Optional: Add visited-set guard for defensive programming
   PHG4Particle* parent = truth.GetParticle(particle->get_parent_id());
-  // if there is a loop of the parent, then we have a problem lol with this while loop btw
+  std::unordered_set<int> visited;
   while (parent)
   {
+    if (!visited.insert(parent->get_track_id()).second)
+    {
+      std::cerr << "PHG4TruthTrackingAction::issPHENIXPrimary - cycle detected in ancestry for track id " << particle->get_track_id() << std::endl;
+      return false;
+    }
     if (isLongLived(parent->get_pid()))
     {

379-386: Remove or guard commented-out debug code before merging.

Leaving commented debug prints in production code adds clutter. Consider removing this block entirely or guarding it behind a verbosity flag if it's needed for future debugging.

generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc (5)

12-16: Remove unused FastJet includes.

fastjet/JetDefinition.hh and fastjet/PseudoJet.hh are included but not used in this particle trigger implementation. These are likely copied from HepMCJetTrigger and should be removed.

♻️ Proposed fix
 `#include` <phool/PHCompositeNode.h>
 `#include` <phool/getClass.h>

-#include <fastjet/JetDefinition.hh>
-
 `#include` <HepMC/GenEvent.h>

-#include <fastjet/PseudoJet.hh>
 `#include` <map>

85-108: Early abort on first bad event in multi-event map loses information.

In process_event, when iterating over PHHepMCGenEventMap, an early return Fun4AllReturnCodes::ABORTEVENT on the first failing event (lines 90, 95, 100) discards any information about which specific event failed. If the map typically contains only one event, this is fine, but if multiple events exist, consider logging which one failed before aborting.


114-121: AddParticles can use insert for efficiency.

The loop can be replaced with a single insert call for better clarity and potential performance.

♻️ Proposed fix
 void HepMCParticleTrigger::AddParticles(const std::vector<int>& particles)
 {
-  for (auto p : particles)
-  {
-    _theParticles.push_back(p);
-  }
-  return;
+  _theParticles.insert(_theParticles.end(), particles.begin(), particles.end());
 }

344-351: Use map::try_emplace or operator[] increment for cleaner map update.

The contains-then-increment pattern can be simplified.

♻️ Proposed fix
-    if (particle_types.contains(pid))
-    {
-      particle_types[pid]++;
-    }
-    else
-    {
-      particle_types[pid] = 1;
-    }
+    particle_types[pid]++;  // operator[] default-initializes to 0 if key absent

302-302: The API cannot distinguish particles from antiparticles—this is an intentional design choice that affects usability.

Line 302 uses std::abs((*iter)->pdg_id()), which combines particles with their antiparticles. The absolute value normalization is applied consistently throughout the matching pipeline: users calling AddParticle(11) or AddParticle(-11) both match via std::abs(trigger_particle) in particleAboveThreshold() (line 363), and all event particles are stored using their absolute pdg_id in the particle_types map. This means electrons (11) and positrons (-11) cannot be distinguished.

If distinguishing particles from antiparticles is important for your trigger use cases, the API would need to preserve the sign of pdg_id throughout the matching logic.

generators/Herwig/HepMCTrigger/HepMCJetTrigger.h (1)

50-51: Add const qualifier to getter methods.

These getters don't modify state and should be marked const for correctness and to allow calling on const references.

♻️ Proposed fix
-  int getNevts(){return this->n_evts;}
-  int getNgood(){return this->n_good;}
+  int getNevts() const { return n_evts; }
+  int getNgood() const { return n_good; }
generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h (2)

8-8: Remove unused FastJet include.

<fastjet/PseudoJet.hh> is not used in this header. This appears to be copied from HepMCJetTrigger.h.

♻️ Proposed fix
 `#include` <fun4all/SubsysReco.h>

-#include <fastjet/PseudoJet.hh>
-
 `#include` <cmath>

69-70: Add const qualifier to getter methods.

These getters don't modify state and should be marked const. This matches the recommended change for HepMCJetTrigger.h.

♻️ Proposed fix
-  int getNevts(){return this->n_evts;}
-  int getNgood(){return this->n_good;}
+  int getNevts() const { return n_evts; }
+  int getNgood() const { return n_good; }
offline/QA/Tracking/StateClusterResidualsQA.cc (3)

33-34: Unused square template function.

This helper is defined but never used in the file. Consider removing it to avoid dead code.

🧹 Proposed fix
 namespace
 {
-  template <typename T>
-  inline T square (T const& t) { return t * t; }
-
   template <class T>
   class range_adaptor

215-235: Unused variables state_z and cluster_z in local coordinates branch.

In the m_use_local_coords == true branch, state_z and cluster_z are declared but never assigned or used, leaving them uninitialized.

🧹 Proposed fix: Move declarations inside branches
-          float state_x; 
-          float state_y;
-          float state_z;
-          float cluster_x;
-          float cluster_y;
-          float cluster_z;
           if (m_use_local_coords == true)
           {
-            state_x = state->get_localX();
-            state_y = state->get_localY();
+            float state_x = state->get_localX();
+            float state_y = state->get_localY();
             Acts::Vector2 loc = geometry->getLocalCoords(state->get_cluskey(), cluster);
-            cluster_x = loc.x();
-            cluster_y = loc.y();
+            float cluster_x = loc.x();
+            float cluster_y = loc.y();
             // ... rest unchanged
           }
           else
           {
-            state_x = state->get_x();
+            float state_x = state->get_x();
             // ... rest unchanged
           }

394-400: Unused histogram manager in EndRun.

The histogram manager is retrieved and asserted but not used. If no cleanup is needed, consider simplifying:

 int StateClusterResidualsQA::EndRun(const int /*unused*/)
 {
-  auto *hm = QAHistManagerDef::getHistoManager();
-  assert(hm);
-
   return Fun4AllReturnCodes::EVENT_OK;
 }
offline/QA/Tracking/StateClusterResidualsQA.h (1)

9-9: Unused include <limits>.

FLT_MAX comes from <cfloat> (line 11); std::numeric_limits is not used.

-#include <limits>
offline/packages/trackreco/PHActsSiliconSeeding.h (1)

179-188: Clarify units for MVTX/beam-spot setters.
Defaults are documented in cm, but the setters accept raw floats. A short Doxygen note (or x_cm/y_cm naming) will reduce accidental mm inputs.

💡 Suggested doc tweak
-  void set_mvtxCenterXY(const float X, const float Y)
+  /// MVTX center in cm (sPHENIX coordinates)
+  void set_mvtxCenterXY(const float X, const float Y)
   {
     m_mvtx_x0 = X;
     m_mvtx_y0 = Y;
   }
-  void set_beamSpotXY(const float X, const float Y)
+  /// Beam spot in cm (sPHENIX coordinates)
+  void set_beamSpotXY(const float X, const float Y)
   {
     m_beamSpotx = X;
     m_beamSpoty = Y;
   }
offline/packages/trackreco/PHActsSiliconSeeding.cc (1)

1174-1207: Simplify MVTX-center offset math in iterateLayers (INTT-only).
All current call sites pass startLayer >= 3, so the layer < 3 branch is dead and the offset math collapses to a no-op. Consider removing it here (or add a note if MVTX layers will be added later).

♻️ Optional simplification
-  // move the fitted circle center the negative of the MVTX center position
-  float x0 = 0.0; // cm
-  float y0 = 0.0;
   for (int layer = startLayer; layer < endLayer; ++layer)
   {
     float layerradius = m_geomContainerIntt->GetLayerGeom(layer)->get_radius();
-    if(layer < 3)
-      {
-	x0 = m_mvtx_x0;
-	y0 = m_mvtx_y0;
-      }
-
-    float xfitradius_moved = fitpars[1] - x0;
-    float yfitradius_moved = fitpars[2] - y0;
-
-    const auto [xplus, yplus, xminus, yminus] = TrackFitUtils::circle_circle_intersection(layerradius, fitpars[0], xfitradius_moved, yfitradius_moved);
-
-    float xp = xplus + x0;
-    float xm = xminus + x0;
-    float yp = yplus + y0;
-    float ym = yminus + y0;
+    const auto [xplus, yplus, xminus, yminus] =
+        TrackFitUtils::circle_circle_intersection(layerradius, fitpars[0], fitpars[1], fitpars[2]);
+
+    float xp = xplus;
+    float xm = xminus;
+    float yp = yplus;
+    float ym = yminus;
offline/packages/TrackingDiagnostics/TrkrNtuplizer.cc (2)

1480-1484: Use _nlayers_maps instead of hard-coded 3.
Keeps MVTX layer selection consistent with configurable layer counts.

♻️ Proposed fix
-          if (layer_local < 3)
+          if (layer_local < _nlayers_maps)

1506-1507: Remove commented-out assignments to reduce clutter.

🧹 Cleanup
-          //fx_hit[n_hit::nhitphielem] = TpcDefs::getSectorId(hitset_key);
-          //fx_hit[n_hit::nhitzelem] = TpcDefs::getSide(hitset_key);
offline/packages/globalvertex/GlobalVertexv2.h (1)

82-99: Consider consolidating duplicated rollover utilities.

The rollover_short and rollover_from_unsignedint functions are identically implemented in GlobalVertexv2.h, MbdVertexv2.h, and SvtxVertex_v2.h. Consider extracting these to a shared utility header to reduce code duplication.

offline/packages/globalvertex/GlobalVertexv3.cc (1)

120-151: Avoid copying the SVTX vertex vector in hot-path getters.
These getters copy svtxit->second into a local vector; a const reference avoids repeated allocations and copies.

♻️ Suggested change
-  GlobalVertex::VertexVector trackvertices = svtxit->second;
+  const auto& trackvertices = svtxit->second;
-  GlobalVertex::VertexVector trackvertices = svtxit->second;
+  const auto& trackvertices = svtxit->second;
-  GlobalVertex::VertexVector trackvertices = svtxit->second;
+  const auto& trackvertices = svtxit->second;
-  GlobalVertex::VertexVector trackvertices = svtxit->second;
+  const auto& trackvertices = svtxit->second;

Also applies to: 153-174, 176-197, 199-231

float _theEtaHigh{1.1};
float _theEtaLow{-1.1};
float _thePtHigh{999.9};
float _thePtLow{-999.9};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent default for _thePtLow between header and constructor.

The header initializes _thePtLow to -999.9, but the constructor initializer list in the .cc file sets it to 0. The constructor value wins at runtime, but this inconsistency is confusing and error-prone if the constructor initialization is ever removed.

🐛 Proposed fix (align header with .cc)
   float _thePtHigh{999.9};
-  float _thePtLow{-999.9};
+  float _thePtLow{0};
📝 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
float _thePtLow{-999.9};
float _thePtHigh{999.9};
float _thePtLow{0};
🤖 Prompt for AI Agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h` at line 88, The
default value for the member _thePtLow is inconsistent: the header
HepMCParticleTrigger.h sets float _thePtLow{-999.9} while the constructor
initializer in the .cc sets it to 0; align them by choosing one canonical
default and making both places match (preferably set the header and the
constructor initializer list for HepMCParticleTrigger to the same value, e.g.,
-999.9 or 0) so the declaration of _thePtLow and the constructor initializer
list in the HepMCParticleTrigger class are identical.

Comment on lines +22 to +26
// PHObject virtual overloads
void identify(std::ostream& os = std::cout) const override;
void Reset() override;
int isValid() const override;
PHObject* CloneMe() const override { return new GlobalVertexv3(*this); }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

CloneMe performs a shallow copy of owned vertices.
Line 26 uses the default copy constructor, which shallow-copies _vtxs raw pointers. Because Reset()/destructor delete these pointers, CloneMe() can lead to double-free or UAF when the clone and original are destroyed.

🛠️ Proposed deep-clone implementation
-  PHObject* CloneMe() const override { return new GlobalVertexv3(*this); }
+  PHObject* CloneMe() const override;
+PHObject* GlobalVertexv3::CloneMe() const
+{
+  auto* clone = new GlobalVertexv3();
+  clone->_id = _id;
+  clone->_bco = _bco;
+  for (const auto& [type, vertices] : _vtxs)
+  {
+    for (const auto* vertex : vertices)
+    {
+      clone->clone_insert_vtx(type, vertex);
+    }
+  }
+  return clone;
+}
🤖 Prompt for AI Agents
In `@offline/packages/globalvertex/GlobalVertexv3.h` around lines 22 - 26, CloneMe
currently returns a shallow copy via the default copy constructor which
duplicates raw pointers in _vtxs and leads to double-free/UAF when
Reset()/destructor run; change CloneMe in class GlobalVertexv3 to perform a deep
clone by creating a new GlobalVertexv3 instance and for each pointer in the
original's _vtxs allocate/clone a fresh vertex object (using the vertex type's
copy constructor or its Clone/CloneMe method if available) and push those new
pointers into the new object's _vtxs, ensuring ownership semantics match
Reset()/destructor and avoiding shared raw pointers between original and clone.

Comment on lines +61 to +71
void set_beam_crossing(short int cross) override
{
if (cross == short_int_max)
{
_beamcrossing = std::numeric_limits<unsigned int>::max();
return;
}

const short int cross_ro = rollover_short(cross);
_beamcrossing = static_cast<unsigned int>(cross_ro);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe cast: negative cross_ro converts to large unsigned value.

At line 70, static_cast<unsigned int>(cross_ro) with a negative cross_ro (possible when input is -32768) produces UINT_MAX or near-max values, colliding with the sentinel. Consider adding a guard:

Proposed fix with validation
 void set_beam_crossing(short int cross) override 
 { 
   if (cross == short_int_max)
   {
     _beamcrossing = std::numeric_limits<unsigned int>::max();
     return;
   }

   const short int cross_ro = rollover_short(cross);
+  if (cross_ro < 0)
+  {
+    // Handle edge case - either reject or map to a valid value
+    _beamcrossing = std::numeric_limits<unsigned int>::max();
+    return;
+  }
   _beamcrossing = static_cast<unsigned int>(cross_ro);
 }
📝 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 set_beam_crossing(short int cross) override
{
if (cross == short_int_max)
{
_beamcrossing = std::numeric_limits<unsigned int>::max();
return;
}
const short int cross_ro = rollover_short(cross);
_beamcrossing = static_cast<unsigned int>(cross_ro);
}
void set_beam_crossing(short int cross) override
{
if (cross == short_int_max)
{
_beamcrossing = std::numeric_limits<unsigned int>::max();
return;
}
const short int cross_ro = rollover_short(cross);
if (cross_ro < 0)
{
// Handle edge case - either reject or map to a valid value
_beamcrossing = std::numeric_limits<unsigned int>::max();
return;
}
_beamcrossing = static_cast<unsigned int>(cross_ro);
}
🤖 Prompt for AI Agents
In `@offline/packages/globalvertex/SvtxVertex_v2.h` around lines 61 - 71,
set_beam_crossing currently casts rollover_short(cross) to unsigned which maps
negative values (e.g. -32768) to large unsigned sentinel values; change
set_beam_crossing to validate the result of rollover_short(cross) before
casting: call rollover_short(cross) into a short int (cross_ro), and if cross_ro
is negative set _beamcrossing to a defined safe value (e.g. 0u or another agreed
clamp) instead of static_cast<unsigned int>(cross_ro); keep the existing
special-case when cross == short_int_max that sets _beamcrossing to
std::numeric_limits<unsigned int>::max(), and update any callers/contract if a
different sentinel is chosen.

Comment on lines +91 to +98
static short int rollover_short(short int cross)
{
if (cross == short_int_max) return short_int_max;
if (cross >= 0) return cross;

const int cross_ro = static_cast<int>(short_int_max) + static_cast<int>(cross); // cross negative
return static_cast<short int>(cross_ro);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: rollover_short(-32768) returns -1, causing sentinel collision.

When cross = -32768:

  1. cross_ro = 32767 + (-32768) = -1
  2. In set_beam_crossing, static_cast<unsigned int>(-1) yields UINT_MAX
  3. This conflicts with the sentinel value for short_int_max (32767)

Both set_beam_crossing(32767) and set_beam_crossing(-32768) result in _beamcrossing = UINT_MAX, making them indistinguishable.

Additionally, the conversion is non-bijective: negative values in [-32767, -1] map via rollover to [0, 32766], the same range as non-negative inputs [0, 32766]. Round-trip set_beam_crossing(x) → get_beam_crossing() will not preserve negative input values.

Proposed fix to handle -32768 edge case
 static short int rollover_short(short int cross)
 {
   if (cross == short_int_max) return short_int_max;
   if (cross >= 0) return cross;

   const int cross_ro = static_cast<int>(short_int_max) + static_cast<int>(cross); // cross negative
+  // Ensure result is non-negative to avoid UINT_MAX collision when cast
+  if (cross_ro < 0)
+  {
+    return 0;  // or handle as special case / error
+  }
   return static_cast<short int>(cross_ro);
 }

Consider documenting the valid input range and expected behavior for negative values, or rejecting out-of-range inputs explicitly.

📝 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
static short int rollover_short(short int cross)
{
if (cross == short_int_max) return short_int_max;
if (cross >= 0) return cross;
const int cross_ro = static_cast<int>(short_int_max) + static_cast<int>(cross); // cross negative
return static_cast<short int>(cross_ro);
}
static short int rollover_short(short int cross)
{
if (cross == short_int_max) return short_int_max;
if (cross >= 0) return cross;
const int cross_ro = static_cast<int>(short_int_max) + static_cast<int>(cross); // cross negative
// Ensure result is non-negative to avoid UINT_MAX collision when cast
if (cross_ro < 0)
{
return 0; // or handle as special case / error
}
return static_cast<short int>(cross_ro);
}
🤖 Prompt for AI Agents
In `@offline/packages/globalvertex/SvtxVertex_v2.h` around lines 91 - 98,
rollover_short currently maps cross == -32768 into the same sentinel slot as
short_int_max causing collisions; update rollover_short to explicitly handle the
minimum short value (use numeric_limits<short>::min() or the defined
short_int_min) and either (a) reject it (throw std::out_of_range or assert) so
set_beam_crossing can reject/document out-of-range inputs, or (b) map it to a
distinct reserved value (not short_int_max) so serialization is bijective;
ensure callers set_beam_crossing/get_beam_crossing handle the new behavior and
update any docs/comments accordingly.

Comment on lines +106 to +112
unsigned int SvtxVertex_v3::covar_index(unsigned int i, unsigned int j) const
{
if (i > j)
{
std::swap(i, j);
}
return i + 1 + (j + 1) * (j) / 2 - 1;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing bounds validation for covariance matrix indices.

The covar_index function doesn't validate that i and j are within bounds (0-2). If called with indices >= 3, it will compute an out-of-bounds index into _err[6], causing undefined behavior.

🛡️ Proposed defensive fix
 unsigned int SvtxVertex_v3::covar_index(unsigned int i, unsigned int j) const
 {
+  if (i > 2 || j > 2)
+  {
+    return 0;  // or throw/assert
+  }
   if (i > j)
   {
     std::swap(i, j);
   }
   return i + 1 + (j + 1) * (j) / 2 - 1;
 }
🤖 Prompt for AI Agents
In `@offline/packages/globalvertex/SvtxVertex_v3.cc` around lines 106 - 112, The
covar_index method lacks bounds checking and can produce out-of-range indexes
into the _err[6] covariance storage; update SvtxVertex_v3::covar_index to
validate that both i and j are within [0,2] before computing the packed index
(keep the existing std::swap(i,j) behavior), and if an index is invalid, fail
fast by throwing a std::out_of_range (or using an assert) with a clear message
mentioning the invalid i/j values and the object, so callers cannot index _err
out-of-bounds.

Comment on lines +1542 to +1549
if (layer_local >= _nlayers_maps && layer_local < _nlayers_intt)
{
int row = InttDefs::getRow(hit_key);
int col = InttDefs::getCol(hit_key);

CylinderGeomIntt* intt_cylinder = dynamic_cast<CylinderGeomIntt*>(_inttGeom->GetLayerGeom(layer_local));
double localcoords[3];
intt_cylinder->find_strip_center_localcoords(InttDefs::getLadderZId(hitset_key),row,col,localcoords);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix INTT layer range check (currently skips most INTT layers).
layer_local < _nlayers_intt only handles the first INTT layer when using global layer indices. Use _nlayers_maps + _nlayers_intt as the upper bound.

🐛 Proposed fix
-          if (layer_local >= _nlayers_maps && layer_local < _nlayers_intt)
+          if (layer_local >= _nlayers_maps && layer_local < (_nlayers_maps + _nlayers_intt))
📝 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
if (layer_local >= _nlayers_maps && layer_local < _nlayers_intt)
{
int row = InttDefs::getRow(hit_key);
int col = InttDefs::getCol(hit_key);
CylinderGeomIntt* intt_cylinder = dynamic_cast<CylinderGeomIntt*>(_inttGeom->GetLayerGeom(layer_local));
double localcoords[3];
intt_cylinder->find_strip_center_localcoords(InttDefs::getLadderZId(hitset_key),row,col,localcoords);
if (layer_local >= _nlayers_maps && layer_local < (_nlayers_maps + _nlayers_intt))
{
int row = InttDefs::getRow(hit_key);
int col = InttDefs::getCol(hit_key);
CylinderGeomIntt* intt_cylinder = dynamic_cast<CylinderGeomIntt*>(_inttGeom->GetLayerGeom(layer_local));
double localcoords[3];
intt_cylinder->find_strip_center_localcoords(InttDefs::getLadderZId(hitset_key),row,col,localcoords);
🤖 Prompt for AI Agents
In `@offline/packages/TrackingDiagnostics/TrkrNtuplizer.cc` around lines 1542 -
1549, The range check for INTT layers incorrectly uses "layer_local <
_nlayers_intt", causing most INTT layers to be skipped; update the condition in
the block that handles INTT hits (where variables like layer_local,
_nlayers_maps, _nlayers_intt, InttDefs, and CylinderGeomIntt appear) so the
upper bound is _nlayers_maps + _nlayers_intt (i.e., use layer_local >=
_nlayers_maps && layer_local < _nlayers_maps + _nlayers_intt) to correctly
detect all INTT layers before calling find_strip_center_localcoords.

TrackSeed* track1 = m_siliconTracks->get(track1ID);

if (seedsToDelete.find(track1ID) != seedsToDelete.end())
if (seedsToDelete.contains(track1ID))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for C++20 or C++17 configuration in build files
echo "=== Checking for C++ standard in CMake files ==="
rg -n "CMAKE_CXX_STANDARD|CXX_STANDARD|c\+\+20|c\+\+2a|std=c\+\+20" --type cmake -A 2

echo -e "\n=== Searching CMakeLists.txt files ==="
fd "CMakeLists.txt" | head -20

echo -e "\n=== Checking root CMakeLists.txt ==="
if [ -f "CMakeLists.txt" ]; then
  head -50 CMakeLists.txt
fi

echo -e "\n=== Checking offline package CMakeLists.txt ==="
if [ -f "offline/CMakeLists.txt" ]; then
  grep -n "CMAKE_CXX_STANDARD\|CXX_STANDARD" offline/CMakeLists.txt || echo "Not found in offline/CMakeLists.txt"
fi

echo -e "\n=== Checking trackreco package CMakeLists.txt ==="
if [ -f "offline/packages/trackreco/CMakeLists.txt" ]; then
  cat offline/packages/trackreco/CMakeLists.txt
fi

Repository: sPHENIX-Test/coresoftware

Length of output: 461


🏁 Script executed:

#!/bin/bash
# Find all CMakeLists.txt files
echo "=== Finding CMakeLists.txt files ==="
find . -name "CMakeLists.txt" -type f 2>/dev/null | head -30

echo -e "\n=== Searching for C++ standard settings (broad search) ==="
rg "CMAKE_CXX_STANDARD|set.*CXX_STANDARD|c\+\+20|c\+\+17|c\+\+2a" -i 2>/dev/null | head -40

echo -e "\n=== Checking root directory structure ==="
ls -la | head -20

echo -e "\n=== Checking offline directory ==="
ls -la offline/ | head -20

echo -e "\n=== Checking trackreco directory ==="
ls -la offline/packages/trackreco/ 2>/dev/null | head -20

Repository: sPHENIX-Test/coresoftware

Length of output: 3120


🏁 Script executed:

#!/bin/bash
# Find the specific file
echo "=== Locating PHSiliconSeedMerger.cc ==="
find . -name "PHSiliconSeedMerger.cc" -type f

echo -e "\n=== Checking if contains() is used elsewhere in codebase ==="
rg "\.contains\(" --type cc --type cpp -l | head -20

echo -e "\n=== Checking line 92 of PHSiliconSeedMerger.cc ==="
if [ -f "offline/packages/trackreco/PHSiliconSeedMerger.cc" ]; then
  sed -n '85,99p' offline/packages/trackreco/PHSiliconSeedMerger.cc | cat -n
else
  echo "File not found at expected location"
fi

echo -e "\n=== Looking for build configuration files ==="
find . -name "*.cmake" -o -name "Makefile*" -o -name "setup.py" -o -name "setup.cfg" 2>/dev/null | head -20

echo -e "\n=== Checking .clang-tidy for any C++ standard hints ==="
head -50 .clang-tidy

Repository: sPHENIX-Test/coresoftware

Length of output: 3795


🏁 Script executed:

#!/bin/bash
# Look for configure.ac or Makefile.am in trackreco
echo "=== Looking for build files in trackreco ==="
ls -la offline/packages/trackreco/ | grep -E "Makefile|configure"

echo -e "\n=== Searching for configure.ac in repo ==="
find . -name "configure.ac" -o -name "configure.in" 2>/dev/null

echo -e "\n=== Checking trackreco Makefile.am if it exists ==="
if [ -f "offline/packages/trackreco/Makefile.am" ]; then
  cat offline/packages/trackreco/Makefile.am
fi

echo -e "\n=== Searching for CXX flags in any Makefile.am ==="
rg "CXXFLAGS|CXX_STANDARD|c\+\+20" 2>/dev/null | head -20

echo -e "\n=== Looking for any contains() usage in .cc files ==="
rg "contains\(" offline/packages/trackreco/ 2>/dev/null

echo -e "\n=== Check if std::set is used elsewhere with contains ==="
rg "std::set" offline/packages/trackreco/ --context 2 2>/dev/null | head -30

Repository: sPHENIX-Test/coresoftware

Length of output: 16578


🏁 Script executed:

#!/bin/bash
# Check the full trackreco configure.ac file
echo "=== Full trackreco/configure.ac ==="
cat offline/packages/trackreco/configure.ac

echo -e "\n=== Check for parent configure.ac or build system defaults ==="
cat configure.ac 2>/dev/null || echo "No root configure.ac"

echo -e "\n=== Check if any compiler version is enforced ==="
rg "AC_PROG_CXX|AX_CXX" offline/packages/trackreco/ 2>/dev/null

echo -e "\n=== Count of contains() usages by file ==="
rg "\.contains\(" offline/packages/trackreco/ -l 2>/dev/null | xargs -I {} sh -c 'echo "{}:"; rg "\.contains\(" "{}" | wc -l'

Repository: sPHENIX-Test/coresoftware

Length of output: 1179


Add C++20 standard flag to build configuration.

The code uses std::set::contains(), a C++20 feature, but offline/packages/trackreco/configure.ac does not explicitly enable C++20 (no -std=c++20 or equivalent flag). This will fail on compilers defaulting to C++17 or earlier. The issue is widespread—six other trackreco source files also use .contains(). Update configure.ac to add the C++20 flag to CXXFLAGS or use AX_CHECK_COMPILE_FLAG to ensure C++20 support.

🤖 Prompt for AI Agents
In `@offline/packages/trackreco/PHSiliconSeedMerger.cc` at line 92, The build
fails because code (e.g., PHSiliconSeedMerger.cc using
seedsToDelete.contains(track1ID)) relies on C++20; update the build
configuration to enable C++20 by modifying configure.ac to add the C++20
standard flag to the compiler flags (either append -std=c++20 to
CXXFLAGS/CXXFLAGS_DEFAULT) or, preferably, use Autoconf’s AX_CHECK_COMPILE_FLAG
(or similar) to detect and add a supported C++20 flag; ensure this change covers
all trackreco sources that call std::set::contains and that the configure check
emits a clear error if no C++20-supporting flag is available.

Comment on lines +114 to +142
for (const auto& cfg : m_pending)
{
if (m_use_local_coords)
{
m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_rphi"))));
m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_z"))));
m_histograms_layer_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_layer_rphi"))));
m_histograms_layer_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_layer_z"))));
m_histograms_phi_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_phi_rphi"))));
m_histograms_phi_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_phi_z"))));
m_histograms_eta_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_eta_rphi"))));
m_histograms_eta_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_local_eta_z"))));
}
else
{
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"))));
m_histograms_layer_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_layer_x"))));
m_histograms_layer_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_layer_y"))));
m_histograms_layer_z.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_layer_z"))));
m_histograms_phi_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_phi_x"))));
m_histograms_phi_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_phi_y"))));
m_histograms_phi_z.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_phi_z"))));
m_histograms_eta_x.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_eta_x"))));
m_histograms_eta_y.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_eta_y"))));
m_histograms_eta_z.push_back(dynamic_cast<TH2 *>(hm->getHisto(std::string(cfg.name + "_eta_z"))));
}
}
Copy link

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-checks after dynamic_cast may cause crashes in process_event.

If hm->getHisto() returns nullptr or the cast fails, null pointers are stored in the histogram vectors. Later calls to Fill() (lines 228-257) will dereference these nulls.

🐛 Proposed fix: Add null-checks or assertions
   for (const auto& cfg : m_pending)
   {
     if (m_use_local_coords)
     {
-      m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_rphi"))));
-      m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_z"))));
+      auto* h_x = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_rphi")));
+      auto* h_y = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_local_z")));
+      if (!h_x || !h_y)
+      {
+        std::cout << PHWHERE << " Failed to retrieve histograms for config: " << cfg.name << std::endl;
+        return Fun4AllReturnCodes::ABORTRUN;
+      }
+      m_histograms_x.push_back(h_x);
+      m_histograms_y.push_back(h_y);
       // ... similar checks for other histograms
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.cc` around lines 114 - 142, The
loop that pushes dynamic_cast results from hm->getHisto into vectors (e.g.,
m_histograms_x, m_histograms_y, m_histograms_layer_x, etc.) can store null
pointers if getHisto() returns nullptr or the cast fails, leading to crashes
later in process_event when Fill() is called; update the loop that iterates over
m_pending (inside the m_use_local_coords branches) to check each returned
pointer before push_back (or assert/throw with a clear message), and only push
valid TH1/TH2 pointers (or substitute a safe no-op placeholder) so that
subsequent process_event accesses of those vectors are guaranteed non-null.
Ensure checks reference the same calls shown (hm->getHisto(std::string(cfg.name
+ "...")) and the dynamic_cast results) and adjust downstream logic in
process_event to assume non-null or handle the placeholder consistently.

Comment on lines +184 to +262
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());
if (!cluster)
{
continue;
}

float state_x;
float state_y;
float state_z;
float cluster_x;
float cluster_y;
float cluster_z;
if (m_use_local_coords == true)
{
state_x = state->get_localX();
state_y = state->get_localY();
Acts::Vector2 loc = geometry->getLocalCoords(state->get_cluskey(), cluster);
cluster_x = loc.x();
cluster_y = loc.y();
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_layer_x[h]->Fill(TrkrDefs::getLayer(state->get_cluskey()), state_x - cluster_x);
m_histograms_layer_y[h]->Fill(TrkrDefs::getLayer(state->get_cluskey()), state_y - cluster_y);
m_histograms_phi_x[h]->Fill(state->get_phi(), state_x - cluster_x);
m_histograms_phi_y[h]->Fill(state->get_phi(), state_y - cluster_y);
m_histograms_eta_x[h]->Fill(state->get_eta(), state_x - cluster_x);
m_histograms_eta_y[h]->Fill(state->get_eta(), state_y - cluster_y);
}
else
{
state_x = state->get_x();
state_y = state->get_y();
state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
cluster_x = glob.x();
cluster_y = glob.y();
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);
m_histograms_layer_x[h]->Fill(TrkrDefs::getLayer(state->get_cluskey()), state_x - cluster_x);
m_histograms_layer_y[h]->Fill(TrkrDefs::getLayer(state->get_cluskey()), state_y - cluster_y);
m_histograms_layer_z[h]->Fill(TrkrDefs::getLayer(state->get_cluskey()), state_z - cluster_z);
m_histograms_phi_x[h]->Fill(state->get_phi(), state_x - cluster_x);
m_histograms_phi_y[h]->Fill(state->get_phi(), state_y - cluster_y);
m_histograms_phi_z[h]->Fill(state->get_phi(), state_z - cluster_z);
m_histograms_eta_x[h]->Fill(state->get_eta(), state_x - cluster_x);
m_histograms_eta_y[h]->Fill(state->get_eta(), state_y - cluster_y);
m_histograms_eta_z[h]->Fill(state->get_eta(), state_z - cluster_z);
}
}
}
++h;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Index desynchronization: continue skips ++h, causing wrong histograms to be filled.

When the charge filter at lines 189-196 triggers continue, the index h is not incremented, but the loop advances to the next config. This causes subsequent configs to fill the wrong histograms.

Example: If m_pending has 3 configs and the first config's charge check fails, h remains 0 while processing the second config, filling m_histograms_x[0] instead of m_histograms_x[1].

🐛 Proposed fix: Increment `h` before `continue`
       if (cfg.charge != 0)
       {
         if ((cfg.charge < 0) && track->get_positive_charge())
         {
+          ++h;
           continue;
         }
         if ((cfg.charge > 0) && !(track->get_positive_charge()))
         {
+          ++h;
           continue;
         }
       }

Alternatively, use index-based iteration:

-    int h = 0;
-    for (const auto& cfg : m_pending)
+    for (size_t h = 0; h < m_pending.size(); ++h)
     {
+      const auto& cfg = m_pending[h];
       if (cfg.charge != 0)
       {
         if ((cfg.charge < 0) && track->get_positive_charge())
         {
           continue;
         }
         ...
       }
       ...
-      ++h;
     }
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.cc` around lines 184 - 262, The
loop over m_pending uses a separate counter h that is skipped when early
continue is taken (in the charge filter), causing histogram indexing to
desynchronize; fix by ensuring h is incremented before every continue in the for
(const auto& cfg : m_pending) loop (i.e., place ++h just before each continue in
the charge checks) or refactor the loop to an index-based iteration (for (size_t
h=0; h<m_pending.size(); ++h) using m_pending[h]) so the histogram index always
matches the config; update references to cfg accordingly (m_pending, h, charge
filter block, and the histogram-fill section).

std::vector<TH2*> m_histograms_eta_z{};
};

#endif // TRACKFITTINGQA_H
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Header guard comment mismatch.

The closing comment says TRACKFITTINGQA_H but the guard is STATECLUSTERRESIDUALSQA_H (line 3).

-#endif  // TRACKFITTINGQA_H
+#endif  // STATECLUSTERRESIDUALSQA_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
#endif // TRACKFITTINGQA_H
`#endif` // STATECLUSTERRESIDUALSQA_H
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.h` at line 181, The header guard
closing comment is incorrect: the guard used is STATECLUSTERRESIDUALSQA_H but
the closing comment reads TRACKFITTINGQA_H; update the trailing comment to match
the guard identifier (STATECLUSTERRESIDUALSQA_H) so the opening and closing
guard identifiers are consistent with the `#ifndef/`#define for
STATECLUSTERRESIDUALSQA_H.

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.