Skip to content

Enable externally-filled surface hits for Simphony optical photon transport - #97

Open
Ningclover wants to merge 3 commits into
ClarkMcGrew:masterfrom
Ningclover:integrate_eicoptics
Open

Enable externally-filled surface hits for Simphony optical photon transport#97
Ningclover wants to merge 3 commits into
ClarkMcGrew:masterfrom
Ningclover:integrate_eicoptics

Conversation

@Ningclover

Copy link
Copy Markdown

Summary

edep-sim already provides the framework for surface sensitive detectors,
EDepSim::HitSurface, photon-detector summarisation, and the
fExternalActions hook on the user event action. This PR makes that
framework usable from an external plugin that transports optical photons
outside Geant4
(e.g. on a GPU) and fills the resulting hits back into the
event.

The changes are deliberately minimal and additive — no existing behaviour
changes for a normal CPU-only run.

Changes

  • EDepSimHitSurface.{hh,cc} — new value-based constructor.
    Adds HitSurface(primaryId, energyDeposit, position, start, pdgEncoding, creatorType, creatorSubtype). Photons transported outside Geant4 have no
    G4Step, so the existing G4Step-based constructor can't be used to
    record their hits. The original constructor is untouched.

  • EDepSimUserEventAction.cc — run external actions before the early
    return.
    The fExternalActions loop is moved to the top of
    EndOfEventAction, ahead of the ionization-hit handling that can return
    early. An external optical plugin must run on every event, including
    events with no ionization hits in any sensitive detector.

  • EDepSimSurfaceSD.cc — null-guard the stacking action. When photon
    transport is offloaded the user stacking action may not be installed;
    SetKillOpticalPhotons is now only called when it is present, avoiding a
    null dereference.

  • EDepSimPersistencyManager.cc — null-guard hit collections. In
    SummarizePhotonDetectors and SummarizeSegmentDetectors, guard against a
    null G4VHitsCollection (which can occur when collections are filled
    externally) before calling GetSize().

  • EDepSimPersistencyManager.cc — use CreateAttValues() in the
    trajectory-point debug print.
    GetAttValues() can return null in this
    path; CreateAttValues() is the safe accessor. This only affects an
    error-logging branch. (Minor correctness fix noticed while working here —
    happy to split it out if preferred.)

Testing

Built against Clark's master and exercised with an external GPU optical
plugin (eic-opticks / simphony). CPU-only runs are unaffected. Branch merges
cleanly into master.

Ningclover and others added 3 commits April 28, 2026 09:55
- Use CreateAttValues() instead of deprecated GetAttValues() in PersistencyManager
- Null-check theStackingAction before calling SetKillOpticalPhotons in SurfaceSD
- Move external actions call before early-return in UserEventAction so GPU optical plugins always run per event

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HitSurface: add a value constructor for photon transport that is
  offloaded from Geant4 (e.g. GPU tracking), where no G4Step exists to
  copy the hit information from. Pure initializer list; no change for
  existing callers or the persistent TG4PhotonHit/ROOT I/O layer.
- PersistencyManager: null-guard the hit collections in
  SummarizePhotonDetectors and SummarizeSegmentDetectors. The G4HCtable
  lists every registered collection, but GetHC() returns null for a slot
  not filled in a given event - possible for sensitive detectors that are
  not attached to a volume (their Initialize() is never called). The old
  code dereferenced unconditionally and segfaulted.

Together these let an external plugin convert offloaded photon hits into a
HitSurface collection that lands in TG4Event.PhotonDetectors under its own
sensitive-detector name, with any persistency backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
void EDepSim::UserEventAction::EndOfEventAction(const G4Event* theEvent) {
EDepSimInfo("Event " << theEvent->GetEventID() << " completed.");

// Run external actions unconditionally first, before the early-return

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Moving this code earlier means that the EDepSim::Trajectory will not be completely filled when the G4UserEventAction is called. I wonder if this means that there should be two call backs (e.g. the current EndOfEventAction() that doesn't modify the trajectories, and a new "PreEndOfEventAction()" that can add stuff).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Actually, a better solution might be to leave the original call at the end, and move this into the "if (!HCofEvent)".

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