Skip to content

Feature: source link filtering#4186

Open
hupereir wants to merge 1 commit intosPHENIX-Collaboration:masterfrom
hupereir:acts_fitter_sourcelink_filter
Open

Feature: source link filtering#4186
hupereir wants to merge 1 commit intosPHENIX-Collaboration:masterfrom
hupereir:acts_fitter_sourcelink_filter

Conversation

@hupereir
Copy link
Contributor

@hupereir hupereir commented Feb 18, 2026

This PR splits the functionalities of GetSurfaceVect() method in two.
The first FilterSourceLinks new method filters the source links to remove measurements from detectors that we don't want to include in the fit (like TPC in TPOT-Silicon fit; TPC and TPOT in silicon only fit, etc.).
this can now be applied whether or not one uses the directed fit.
The second method, GetSurfaceVect, specific to the directed fit, selects the list of surfaces from the filtered sourcelinks

this should have no impact on the current workflow, but should make it easy to (for instance) remove Micromegas clusters from the default ACTS fit, even if the clusters are on the track.

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)

PR Summary: Source Link Filtering for ACTS Track Fitter

Motivation

This PR refactors the ACTS track fitting algorithm to separate the concern of filtering detector measurements from the surface extraction logic. Previously, filtering of source links (measurements to be excluded from track fits) was tightly coupled with other parts of the track fitting workflow. The change enables flexible exclusion of specific detector measurements independent of whether directed navigation is used, making it easier to exclude detectors like Micromegas clusters from default ACTS fits.

Key Changes

  • New method filterSourceLinks(): Implements centralized filtering logic that removes source links based on current fit configuration:

    • Excludes TPC surfaces when fitting silicon+micromegas tracks
    • Excludes micromegas surfaces when not required by the fit configuration
    • Excludes both TPC and micromegas when performing silicon-only fits
  • New method getSurfaceVector(): Extracts surfaces directly from source links without applying filters, used specifically for directed navigation path

  • Control flow restructuring: Filtering is now applied early in the track fitting loop before surface checking, removing empty source link lists before proceeding with surface extraction

  • API changes:

    • Added SourceLinkVec filterSourceLinks(const SourceLinkVec& sourceLinks) const
    • Added SurfacePtrVec getSurfaceVector(const SourceLinkVec& sourceLinks) const
    • Removed previous combined surface/filter method

Potential Risk Areas

  • Reconstruction behavior: The change decouples measurement filtering from directed navigation—careful review needed to ensure the filtering criteria remain correctly applied in all fit pathways (standard navigation, silicon+MM, silicon-only)
  • Performance: Early filtering should have minimal impact, but early-exit logic for empty source links may affect fit rate in edge cases
  • Configuration interactions: Multiple boolean flags control filtering behavior (m_fitSiliconMMs, m_forceSiOnlyFit, m_useMicromegas)—ensure combinations are well-tested

Intended Impact

The PR is designed to be non-breaking for current workflows while enabling future flexibility in detector measurement exclusion strategies.

Note: AI-generated analysis aids the review but careful manual inspection of the filtering logic and control flow changes is recommended.

…move measurements whether or not we run the direct navigations.

GetSurfaceVect, as used for the direct navigation, does not apply any filtering.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

The changes refactor source link filtering and surface extraction logic in PHActsTrkFitter. A new filterSourceLinks() method replaces inline filtering, while getSurfaceVector() now returns surfaces directly instead of modifying an output parameter. The method signatures are updated for improved const-correctness.

Changes

Cohort / File(s) Summary
PHActsTrkFitter Interface & Implementation
offline/packages/trackreco/PHActsTrkFitter.h, offline/packages/trackreco/PHActsTrkFitter.cc
Added two new public helper methods: filterSourceLinks() to prune source links based on detector inclusion rules, and getSurfaceVector(const SourceLinkVec&) that returns surfaces derived from source links. Removed the old getSurfaceVector() overload that used an output parameter. Refactored control flow to call filtering before surface extraction, affecting track fitting paths for directed navigation and Silicon MM fits.
✨ Finishing Touches
  • 📝 Generate docstrings

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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
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: 3

Comment on lines +546 to +548
// filter sourcelinks to remove detectors that we don't want to include in the fit
sourceLinks = filterSourceLinks( sourceLinks );

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unconditional filterSourceLinks call changes behaviour for non-default flag combinations.

Previously, all filtering lived inside the old getSurfaceVector, which was only reached when m_fitSiliconMMs || m_directNavigation. After this change, the micromegas exclusion (!m_useMicromegas) and the silicon-only cut (m_forceSiOnlyFit) are applied to every fit path, including the standard TPC fit.

For the default settings (m_useMicromegas = true, m_forceSiOnlyFit = false) there is no change. However, any workflow that sets m_useMicromegas = false without enabling directed navigation will now silently drop micromegas clusters from the fit, which was not the case before.

Given the PR's stated intent ("filtering can be applied regardless of directed fit"), this appears deliberate, but it warrants a brief comment near line 547 to make the scope of the change explicit for future maintainers.

💬 Suggested in-code clarification
-      // filter sourcelinks to remove detectors that we don't want to include in the fit
+      // filter sourcelinks to remove detectors excluded from the fit;
+      // applied unconditionally (i.e. independent of directed-navigation mode)
       sourceLinks = filterSourceLinks( sourceLinks );

Comment on lines +969 to +985
SourceLinkVec filtered;
for (const auto& sl : sourceLinks)
{
const ActsSourceLink asl = sl.get<ActsSourceLink>();
if (Verbosity() > 1)
{
std::cout << "SL available on : " << asl.geometryId() << std::endl;
}

const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
if (m_fitSiliconMMs)
{
// skip TPC surfaces
if (m_tGeometry->maps().isTpcSurface(surf))
{
continue;
}

// also skip micromegas surfaces if not used
if (m_tGeometry->maps().isMicromegasSurface(surf) && !m_useMicromegas)
{
continue;
}
}
// skip TPC surfaces for fitSilicon MMs
if (m_tGeometry->maps().isTpcSurface(surf) && m_fitSiliconMMs)
{ continue; }

if (m_forceSiOnlyFit)
{
if (m_tGeometry->maps().isMicromegasSurface(surf) || m_tGeometry->maps().isTpcSurface(surf))
{
continue;
}
}
// skip micromegas surfaces if not used
if (m_tGeometry->maps().isMicromegasSurface(surf) && !m_useMicromegas)
{ continue; }

// skip everything but silicons if only silicon fit is required
if (m_forceSiOnlyFit && (m_tGeometry->maps().isMicromegasSurface(surf) || m_tGeometry->maps().isTpcSurface(surf)) )
{ continue; }
Copy link
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:

# Locate and examine isTpcSurface and isMicromegasSurface implementations
rg -n "void.*isTpcSurface|bool.*isTpcSurface" --type=cpp -A10 -B2

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 2240


🏁 Script executed:

# Also search for isMicromegasSurface
rg -n "void.*isMicromegasSurface|bool.*isMicromegasSurface" --type=cpp -A10 -B2

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 2202


🏁 Script executed:

# Check the class containing these methods - likely in maps
fd -e h -e hpp | xargs rg -l "isTpcSurface|isMicromegasSurface" | head -5

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 122


🏁 Script executed:

# Search for findSurface return type and behavior
rg -n "findSurface" --type=cpp --type=hpp -B2 -A5 | head -60

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 105


🏁 Script executed:

# Search for findSurface definition in Acts geometry code
rg -n "findSurface" --type=cpp --type=h -B5 -A10 | grep -A10 "findSurface.*{" | head -50

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 60


🏁 Script executed:

# Check if findSurface is documented or used with null checks elsewhere
rg -n "findSurface" offline/packages/trackreco/PHActsTrkFitter.cc -B3 -A3

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 674


🏁 Script executed:

# Search Acts library to understand findSurface behavior
fd -type f -name "*.h" | xargs rg -l "findSurface" | head -5

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 355


🏁 Script executed:

# Look for TrackingGeometry or tGeometry type to understand findSurface
rg -n "tGeometry" offline/packages/trackreco/PHActsTrkFitter.cc | head -10

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 501


🏁 Script executed:

# Check if there are any examples in the codebase where findSurface result is checked for null
rg -n "findSurface" -B2 -A2 | grep -A2 -B2 "if.*nullptr\|if.*!.*surf\|if.*surf.*==" | head -30

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 60


Add null-check after findSurface to prevent undefined behavior.

isTpcSurface and isMicromegasSurface both dereference their argument without null-checking (lines 34 and 39 of ActsSurfaceMaps.cc). Since findSurface returns nullptr when a geometry ID is absent from the Acts geometry (e.g., stale clusters or geometry mismatches), passing the unchecked result to these functions at lines 976, 980, and 984 causes undefined behavior.

Suggested fix
     const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
+    if (!surf) { continue; }

     // skip TPC surfaces for fitSilicon MMs
📝 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
SourceLinkVec filtered;
for (const auto& sl : sourceLinks)
{
const ActsSourceLink asl = sl.get<ActsSourceLink>();
if (Verbosity() > 1)
{
std::cout << "SL available on : " << asl.geometryId() << std::endl;
}
const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
if (m_fitSiliconMMs)
{
// skip TPC surfaces
if (m_tGeometry->maps().isTpcSurface(surf))
{
continue;
}
// also skip micromegas surfaces if not used
if (m_tGeometry->maps().isMicromegasSurface(surf) && !m_useMicromegas)
{
continue;
}
}
// skip TPC surfaces for fitSilicon MMs
if (m_tGeometry->maps().isTpcSurface(surf) && m_fitSiliconMMs)
{ continue; }
if (m_forceSiOnlyFit)
{
if (m_tGeometry->maps().isMicromegasSurface(surf) || m_tGeometry->maps().isTpcSurface(surf))
{
continue;
}
}
// skip micromegas surfaces if not used
if (m_tGeometry->maps().isMicromegasSurface(surf) && !m_useMicromegas)
{ continue; }
// skip everything but silicons if only silicon fit is required
if (m_forceSiOnlyFit && (m_tGeometry->maps().isMicromegasSurface(surf) || m_tGeometry->maps().isTpcSurface(surf)) )
{ continue; }
SourceLinkVec filtered;
for (const auto& sl : sourceLinks)
{
const ActsSourceLink asl = sl.get<ActsSourceLink>();
const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
if (!surf) { continue; }
// skip TPC surfaces for fitSilicon MMs
if (m_tGeometry->maps().isTpcSurface(surf) && m_fitSiliconMMs)
{ continue; }
// skip micromegas surfaces if not used
if (m_tGeometry->maps().isMicromegasSurface(surf) && !m_useMicromegas)
{ continue; }
// skip everything but silicons if only silicon fit is required
if (m_forceSiOnlyFit && (m_tGeometry->maps().isMicromegasSurface(surf) || m_tGeometry->maps().isTpcSurface(surf)) )
{ continue; }

Comment on lines +1001 to 1003
const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
surfaces.push_back(surf);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Null Surface* can be silently inserted into the directed-navigation sequence.

findSurface returns nullptr for unrecognised geometry IDs, and the result is pushed into surfaces without any guard. The directed Kalman fitter dereferences every element of surfSequence, so a single null entry will crash.

🛡️ Suggested guard
     const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
+    if (!surf) { continue; }   // drop source links whose surface is absent in the geometry
     surfaces.push_back(surf);
📝 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
const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
surfaces.push_back(surf);
}
const auto* const surf = m_tGeometry->geometry().tGeometry->findSurface(asl.geometryId());
if (!surf) { continue; } // drop source links whose surface is absent in the geometry
surfaces.push_back(surf);
}

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

Comments