Skip to content

Fix weighting bugs and trampoline pure virtual crash#142

Open
austinschneider wants to merge 5 commits into
Harvard-Neutrino:sbn_geometry_loaderfrom
austinschneider:fix/sdm-bugfixes-weighting
Open

Fix weighting bugs and trampoline pure virtual crash#142
austinschneider wants to merge 5 commits into
Harvard-Neutrino:sbn_geometry_loaderfrom
austinschneider:fix/sdm-bugfixes-weighting

Conversation

@austinschneider

@austinschneider austinschneider commented May 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • WeightingUtils: use intersection-based GetAvailableTargets (consistent with Injector) and guard against divide-by-zero when total_prob == 0
  • Decay naming convention: rename TotalDecayWidth(InteractionRecord) to TotalDecayWidthAllFinalStates and TotalDecayWidthForFinalState to TotalDecayWidth, matching the CrossSection convention where the InteractionRecord overload returns the per-final-state value
  • Trampoline GC fix: Python Decay/CrossSection subclasses crashed with "Tried to call pure virtual function" when their methods were called from C++ after the Python object was garbage-collected. Fixed by lazy-initializing the trampoline self reference and adding keep_alive to InteractionCollection constructors

Ports/adapts fixes from sdm commits dd0ad4a, 49d253b, and introduces a proper fix for the trampoline lifetime issue that previously required the DarkNewsTables.Holder workaround.

Test plan

  • All 43 C++ unit tests pass (UnitTest_HNLDecay)
  • All 14 Python tests pass (test_hnl_decays.py)
  • Manual verification: Python Decay subclass override survives GC through InteractionCollection

Port fix from upstream/sdm (dd0ad4a): compute available targets using
the intersection list (consistent with how Injector does it), and guard
against divide-by-zero when total_prob is zero.
Port naming convention from upstream/sdm (49d253b):

- TotalDecayWidth(InteractionRecord) now means per-final-state width
  (was TotalDecayWidthForFinalState)
- TotalDecayWidthAllFinalStates(InteractionRecord) is the sum over all
  channels (was TotalDecayWidth)
- Same rename for TotalDecayLength/TotalDecayLengthForFinalState
- TotalDecayWidth(ParticleType) is unchanged

This matches the CrossSection naming convention where the method taking
an InteractionRecord returns the value for the specific final state in
the records signature.
…m C++

The pyDecay/pyCrossSection trampoline self member was never initialized
during normal construction. When the Python object was garbage-collected
while C++ held a shared_ptr, pybind11::get_override could no longer find
the Python override, causing the pure virtual fallthrough.

Fix with two layers of defense:

1. SELF_OVERRIDE macros now lazily initialize self via get_object_handle
   on first call. Once set, the mutable self member prevents the Python
   object from being collected (prevents future GC).

2. InteractionCollection pybinding constructors now use keep_alive to
   prevent GC of passed Decay/CrossSection Python objects for the
   lifetime of the collection.

Together these eliminate the need for the DarkNewsTables Holder pattern
and the manual reference-keeping in Weighter.py (though those remain as
additional safety).
Copilot AI review requested due to automatic review settings May 24, 2026 21:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates decay-weighting and decay API semantics to align with cross-section conventions, and hardens the pybind11 trampoline/bindings to prevent “pure virtual call” crashes when Python subclasses are garbage-collected while still referenced from C++.

Changes:

  • Fix weighting target selection to use intersection-based GetAvailableTargets(...) and add a total_prob == 0 guard to avoid divide-by-zero in WeightingUtils.
  • Rename/realign Decay width/length APIs so InteractionRecord overloads represent per-final-state quantities and introduce *AllFinalStates for totals; update call sites, tests, and Python bindings accordingly.
  • Improve Python subclass lifetime handling via lazy trampoline self initialization and pybind11::keep_alive on InteractionCollection constructors.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/python/test_hnl_decays.py Updates test to use renamed Decay API (TotalDecayWidth(record) for per-final-state).
resources/processes/DarkNewsTables/DarkNewsDecay.py Renames DarkNews decay width methods to match new Decay naming (needs follow-up fix per comment).
projects/utilities/public/SIREN/utilities/Pybind11Trampoline.h Adds lazy self handle initialization and new override macros (incl. reference-return variants).
projects/interactions/public/SIREN/interactions/pyDecay.h Updates trampoline interface to new Decay width/length method names.
projects/interactions/public/SIREN/interactions/pyDarkNewsDecay.h Updates DarkNews trampoline interface to new Decay width naming.
projects/interactions/public/SIREN/interactions/InteractionCollection.h Renames collection decay total helpers to *AllFinalStates.
projects/interactions/public/SIREN/interactions/HNLDipoleDecay.h Renames/realigns decay width method overrides with new convention.
projects/interactions/public/SIREN/interactions/HNLDecay.h Renames/realigns decay width method overrides with new convention.
projects/interactions/public/SIREN/interactions/ElectroweakDecay.h Renames/realigns decay width method overrides with new convention.
projects/interactions/public/SIREN/interactions/Decay.h Updates Decay abstract interface: adds TotalDecayWidthAllFinalStates(record) and TotalDecayLengthAllFinalStates(record).
projects/interactions/public/SIREN/interactions/DarkNewsDecay.h Updates DarkNewsDecay override signatures to the new Decay interface.
projects/interactions/private/test/HNLDecay_TEST.cxx Updates C++ unit tests to use TotalDecayWidth(record) for per-final-state widths.
projects/interactions/private/pyDecay.cxx Updates trampoline implementations to match renamed methods and use updated override macros.
projects/interactions/private/pyDarkNewsDecay.cxx Updates DarkNews trampoline implementations to match renamed methods and updated override macros.
projects/interactions/private/pybindings/InteractionCollection.h Adds keep_alive to constructors and renames exposed decay totals to *AllFinalStates.
projects/interactions/private/pybindings/HNLDipoleDecay.h Updates Python bindings to expose TotalDecayWidthAllFinalStates and per-final-state TotalDecayWidth(record).
projects/interactions/private/pybindings/HNLDecay.h Updates Python bindings to expose TotalDecayWidthAllFinalStates and per-final-state TotalDecayWidth(record).
projects/interactions/private/pybindings/ElectroweakDecay.h Updates Python bindings to expose TotalDecayWidthAllFinalStates and per-final-state TotalDecayWidth(record).
projects/interactions/private/pybindings/Decay.h Expands Decay Python API surface and binds the new width/length methods.
projects/interactions/private/pybindings/DarkNewsDecay.h Updates DarkNewsDecay Python bindings to match the new *AllFinalStates API.
projects/interactions/private/InteractionCollection.cxx Implements renamed decay total helpers and delegates to dec->TotalDecayWidthAllFinalStates(...).
projects/interactions/private/HNLDipoleDecay.cxx Implements renamed width functions and updates internal callers to per-final-state TotalDecayWidth(record).
projects/interactions/private/HNLDecay.cxx Implements renamed width functions and updates internal callers to per-final-state TotalDecayWidth(record).
projects/interactions/private/ElectroweakDecay.cxx Implements renamed width functions and updates internal callers to per-final-state TotalDecayWidth(record).
projects/interactions/private/Decay.cxx Adds TotalDecayLengthAllFinalStates implementation and realigns length computations with renamed widths.
projects/interactions/private/DarkNewsDecay.cxx Adds TotalDecayWidthAllFinalStates(record) and updates FinalStateProbability to use per-final-state TotalDecayWidth(record).
projects/injection/public/SIREN/injection/Weighter.tcc Updates to use InteractionCollection::TotalDecayLengthAllFinalStates.
projects/injection/private/WeightingUtils.cxx Uses intersection-based available targets, switches to per-final-state decay length, and guards divide-by-zero.
projects/injection/private/Injector.cxx Switches to per-final-state TotalDecayLength(record) when building decay probabilities.
projects/distributions/private/secondary/vertex/SecondaryPhysicalVertexDistribution.cxx Uses TotalDecayLengthAllFinalStates when computing total interaction/decay competition.
projects/distributions/private/secondary/vertex/SecondaryBoundedVertexDistribution.cxx Uses TotalDecayLengthAllFinalStates when computing total interaction/decay competition.
projects/distributions/private/primary/vertex/RangePositionDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
projects/distributions/private/primary/vertex/PrimaryPhysicalVertexDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
projects/distributions/private/primary/vertex/PrimaryBoundedVertexDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
projects/distributions/private/primary/vertex/PointSourcePositionDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
projects/distributions/private/primary/vertex/FixedTargetPositionDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
projects/distributions/private/primary/vertex/ColumnDepthPositionDistribution.cxx Uses TotalDecayLengthAllFinalStates in vertex sampling/probability.
Comments suppressed due to low confidence (1)

resources/processes/DarkNewsTables/DarkNewsDecay.py:335

  • DarkNewsDecay in C++ calls the overloaded TotalDecayWidth(...) with both ParticleType (total width) and InteractionRecord (per-final-state). This Python override currently assumes record is always an InteractionRecord and will fail when invoked with a ParticleType (e.g., via Decay::TotalDecayWidth(ParticleType)), breaking calls like TotalDecayWidthAllFinalStates fallback paths. Update this method to dispatch on the argument type (handle ParticleType vs InteractionRecord), and consider making TotalDecayWidthAllFinalStates take only an InteractionRecord and delegate to the ParticleType case, reusing the cached self.total_width rather than recomputing integrals.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The pyDecay trampoline called SELF_OVERRIDE_PURE with
GetPossibleSignaturesFromParents (plural, the CrossSection method
name) instead of GetPossibleSignaturesFromParent (singular, the
Decay method name). This caused Python Decay subclasses to crash
with 'pure virtual function called' when the C++ Injector tried
to enumerate decay signatures.
…onTree

GenerateEvent returns InteractionTree by value, but the pybind binding
used the default copy policy. InteractionTree contains shared_ptr
members that make the implicit copy constructor incompatible with
pybind11 copy semantics. Changed to return_value_policy::move.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.

Comment on lines 300 to 333
@@ -328,7 +328,7 @@ def TotalDecayWidth(self, arg1):
self.total_width = self.dec_case.total_width()
return self.total_width

def TotalDecayWidthForFinalState(self, record):
def TotalDecayWidth(self, record):
sig = self.GetPossibleSignatures()[0]
if (
Comment on lines +40 to +41
std::set<siren::dataclasses::ParticleType> available_targets_list = detector_model->GetAvailableTargets(intersections, DetectorPosition(record.interaction_vertex));
std::set<siren::dataclasses::ParticleType> available_targets(available_targets_list.begin(), available_targets_list.end());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants