List of Encountered Volumes

Dear experts,
I am unsuccessfully trying to get a “list of encountered volumes” that leads up to a certain event entering a sensitive detector. The list should not simply include all volumes that are encountered in that event by any secondary, but specifically only those that are relevant for the particle to reach the SD volume…

I am aware of the encounteredVolumeFilter, but a) I would need a list to further process the data externally and b) with biasing, this sometimes shows incomplete segments of the trajectory, as secondaries / clones are originating from biasing boundaries. I would prefer having a separate, complete list for each hit in the event.

Does anyone have suggestions on how to achieve this? Thanks a lot in advance :slight_smile:

I’m sure there must be tools already in existence (like encounteredVolumeFilter), but I can think of a way to do it by hand.

In some thread-local singleton, create a std::map<G4int, std::set<G4String> >, which is keyed off the TrackID (and which you clear() at the start of each new event). In a stepping action, for each step of each track, add the step’s PV volume name (including copy number, etc., or build a “volume path” from all the mothers) to the std::set<> for that track, by accessing the singleton.

Then, in your SD, if you decide the step in your target volume is relevant, access the singleton map above, and copy the std::set<> for that track into your output, however you want to do it.

I know this is kind of a brute force solution. You might also be able to do something by configuring your job to build a G4Trajectory with every track. Then you could iterate through the points on that track in your SD, and query a locally instantiated G4Navigator to get the volume corresponding to each point.

hm, thanks for the suggestion. i think that would come with a massive penalty on performance, as I have only very few hits in a very large number of events.
the second option with a navigator sounds more promising, I will have to look into that!

I don’t disagree about the performance hit, especially if your “event efficiency” is low. Some of our simulations usually have at least one detector hit per event, so that’s less of an issue for us.

The navigator seems to work really well and efficiently, but make sure you instantiate your own load version (and register the world). If you try to use the tracking navigator for this, it’ll mess up the caching and you’ll start getting really strange “track has moved” errors :open_mouth:

regarding the performance hit: maybe it is not as bad, as I only need to do all the expensive stuff once when entering the next volume - not in every step. and if i now understand correctly, the singleton „memory tree“ exists once for the event, and is not duplicated around for each secondary!?

with the navigator solution: would this not be exactly the same issue? except for when the history of points in the trajectory is not accessible somewhere already anyways.

Ah, that’s clever! I was thinking real brute force, where you get the PV at every step, and let std::set<> take care of maintaining uniqueness.

Yes. My thought was that the singleton exists once per thread for the whole job – at each event, you call clear() to discard the contents and start over. The reason I suggested using a std::map keyed off the track ID is that there are processes which will put a track into “Suspend”, go and track the secondaries first, and then come back to the “main” track. If you just maintained the std::set for “the current track” that method (used by G4Cherenkov, for instance) would mess it up.

With the Navigator method, Geant4 creates a G4Trajectory object, and it takes care of keeping it up to date (Tracking — Book For Application Developers 11.1 documentation). However, I think I want to back away from recommending this. Apparently there’s no clean interface for you to start with a G4Track and get its matching G4Trajectory. You can get the G4TrajectoryContainer from G4Event, and then you have to manually loop over the contents to find the trajectory with the matching TrackID :frowning:

Anyway, the G4Trajectory object is the “history of points.” The G4Track only contains the current state of the particle, not its history (despite the name).

std::forward_list<> has less performance penalty, when the hit/event ratio is skewed enough. I like that idea :slight_smile:

Thats an important point. I additionally have to manually keep track of all the ancestor IDs. At the moment of impact to the SD, only the current track ID and G4Track::GetParentID() is defined anymore: Get G4Track by trackID

update: Geant4 is epic :slight_smile: Performance-penalty in a quick test seems to be surprisingly low, takes only about 40% longer in the qt gui (with /vis/disable & /tracking/storeTrajectory 0)

G4WT1 > hit! SensorID: 8 in TrackID 77
G4WT1 > Volumes for TrackID: 77
G4WT1 >    vol: World
G4WT1 >    vol: shielding
G4WT1 >    vol: World
G4WT1 >    vol: shielding
G4WT1 > Ancestors: 
G4WT1 >    Volumes for AncestorID: 23
G4WT1 >       vol: shieldingFront_mesh023_Lead
G4WT1 >    Volumes for AncestorID: 1
G4WT1 >       vol: shieldingFront_mesh023_Lead
G4WT1 >       vol: World
G4WT1 >       vol: scintillatorModule_mesh029_Glass
G4WT1 >       vol: World
G4WT1 >       vol: scintillatorModule_mesh000_Kevlar
G4WT1 >       vol: World
G4WT1 >       vol: enclosure_mesh000_Kevlar

Nice! Glad it worked, and your report for the full ancestry is excellent. You must have gaps between your successive experimental volumes (which means my idea of a std::set<> wouldn’t have been quite right).

good catch with the gap: I had started the recursion for ancestorIDs with parentID(trackID), so the direct mother track was always skipped. it was a very short track from biasing boundary to the test SD volume anyways, so one world missing…

the unique set would still have worked, but checking for duplicates is probably too expensive at that level.
the hit/event ratio is (with 1/1000 biasing from origin to SD volumes) below 1/1e5, fortunately :innocent:

is there a way to define the additional stepping action anywhere but in the world volume? the detector construction is an ensemble of tessellated objects directly placed into the world LV

The stepping action is a UserAction registered into the RunManager, so There Can Be Only One™. What you’re really asking for is an SD :slight_smile:

You could attach a “volume recorder” SD to all your volumes, in addition to your target (for your target, you could use Geant4’s “multi-SD” thing, and register both the “volume recorder” and your actual target SD. If you create just one SD pointer, and register it into all the volumes, you wouldn’t need any sort of external singleton to store the data.

hm. intriguing at first look, but challenging at second: the history of track ids could be incomplete that way, if secondaries are created in the world volume for either physics or forced/biasing reasons

Each G4Region may have its unique UserSteppingAction. The world volume belongs by default to “DefaultRegionForTheWorld” region.

  • define a new region and make all the volumes in your world belong to this new region
  • instantiate a UserSteppingAction object and set it to “DefaultRegionForTheWorld” region.

N.B. UserSteppingAction must be thread-local. Instantiate it and set to the “DefaultRegionForTheWorld” region through your UserActionInitialization::Build() method.

1 Like

thanks for the suggestion, I will try that. In that way i define the tracking action everywhere and the stepping action in only half the encountered volumes, as the world is in every second step.

can there be multiple root-volumes in the region?

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.