Identification of unique physical volumes with IDs

Dear All,

We have an application where there are several photon detectors, consisting of some plastic scintillators opticazlly coupled with SiPMs. Because of this we have only 6 physical volumes representing the SiPMs coupled to each scintillator, which in turn is placed 8 times, hence we have 48 total actual placements of the SiPMs. What we would like to do is to assign a unique ID to each SiPM PV at the start of the run (after the geometry has been closed), and at tracking time, we would like to save the ID of the SiPM where the optical photon gets detected.

Assigning unique IDs before start of tracking is quite trivial (“browsing” through the entire geometry tree), however it is not clear how to retrieve these information at tracking/stepping level.

We thought to use the G4TouchableHistory (taken from the G4Step), but looking at that class it is not clear how to assign a unique ID to the “touched volume”. In addition, I believe that the G4TouchableHistory instances do not exists before the start of the tracking, do they?

We tried to go through the guide and through other similar questions, but we could not find an answer suitable for our purpose. Does G4 provide any support for doing similar things?

With best regards,
Francesco

1 Like

The unique identifier for a physical volume is the copy number (accessed via GetCopyNo(), GetCopyNumber(), or GetReplicaNumber() depending on which class you happen to have a pointer for, G4VPhysicalVolume or G4VTouchable).

If you are doing the emplacements “manually” (i.e., with G4PVPlacement) the way you described, you cannot get globally unique copy numbers for your SiPMs, as you already know. In what follows, I will assume that you’ve assigned unique copy numbers to each SiPM placed with the scintillator assembly volume, and also assigned unique copy numbers to each of the scintillator assembly placements.

What you can do at tracking time is make use of the G4Touchable from the G4Step. From touchable->GetCopyNumber(), you can get the SiPM’s copy number, iSiPM. Then you can go up the placement tree, what’s called the touchable’s “history”, and get touchable->GetCopyNumber(1) for the copy number of the SiPM’s parent volume, touchable->GetCopyNumber(2) for the grandparent volume, and so on. If the SiPM’s are placed directly into your scintillator assembly, then iScint = touchable->GetCopyNumber(1) is the scintillator’s copy number.

So now you can construct your unique ID as iScint6 + iSiPM, or iScint100+iSiPM for human readability, or whatever you like. You don’t need to do this after the geometry has been closed; you can do it in your stepping action, or sensitive detector, or whatever.

2 Likes

Thank you so much for your help!

Actually the geometry is built from a gdml with a python based software. So in the G4 application we just import the geometry. However, we should be able to twick the code for gdml to let it use copy numbers.

Anyway it seems that the only way to get unique IDs is by recourring the geometry tree at each step. This potentially makes the entire simulation slower. Hopefully not too much.

There are ways to handle this efficiently, either on-demand or at the start of the run. On demand is probably the easiest, since you don’t have to write new code, but you do have to be careful about resetting a buffer at the start of every run (e.g., via your BeginOfRunAction(), or in your ConstructSDandField() function).

Create a lookup table (std::map<G4VPhysicalVolume*, G4int>) where the key is a PV pointer, and the value is the unique ID. In your SD code, use the touchable = theStep->GetPostStepPoint()->GetTouchable() to get the PV of the hit. If it’s in the lookup table, use the associated unique ID. If not, use the touchable hierarchy (which is already built) to get the associated scintillator placement and compute the unique ID as described above.

It is important that you clear out the lookup table every time the geometry is built. It is quite common for memory addresses in the geometry to be reused, so you can end up with a stale pointer in the table showing up later, and messing up your results.

1 Like

I have been having a similar problem and am hoping to get clarification here.

My entire geometry is defined using GDML, I find it convenient for rapid prototyping and have a detector construction class that can use the auxiliary tags to apply visualization attributes to volumes as well as tag which volumes are to have which sensitive detector attached.

I have a gamma-ray detector that I have defined to be an aluminum shape with two daughter volumes. The first daughter volume is just the vacuum of the PMT, the second is a chunk of silicone that, in turn, has a daughter volume, a block of NaI(Tl) to which my sensitive detector is attached. This detector is placed 24 times on the sides of a cube as follows:

I was faced with the conundrum of having the sensitive detector class’s ProcessHits function determine which volume it was in and determined that something like:
step->GetTrack()->GetVolume()->GetCopyNo();
was a decent choice seemingly the minimum amount of nested memory access. Then I realized that I didn’t see any way to set copy number in the GDML manual. By reading the schema I discovered that the <physvol ...> tag accepted attributes name and copynumber as follows: <physvol name="Detector07" copynumber="7"> So I set those for each of my placements of the detector logical volume.

<physvol name="Detector08" copynumber="8">
  <volumeref ref="Detector"/>
  <positionref ref="DetectorTranslation8"/>
  <rotationref ref="DetectorRotation8"/>
</physvol>

Unfortunately, on doing this yesterday, I discovered that the copy number does not propagate down to the daughters of the placed logical volume, as seen in the scene tree of my visualization:
scene_tree

I understand that I could get the touchable as described above and work my way up the touchable tree to get to that copy number from “down in the NaiCrystal_PV” but I would prefer to not require so much memory access in my process hits function. The more pointer chasing I have the more bits of potentially uncached memory accesses I have, on top of the string compares looking for a name to tell ProcessHits “this is the bit you need to look for a copy number in.”

To avoid this inefficiency I have since been looking for a way to traverse the hierarchy at detector construction so that I could take the copy number of the “Detector” and propagate it down to the “NaiCrystal_PV”. In my search I discovered this exchange in the old forum:
https://hypernews.slac.stanford.edu/HyperNews/geant4/get/geometry/116.html

If I am reading this correctly, my idea for this process is doomed because there is only ever 1 “NaiCrystal_PV” and it is just touchable from many locations, is this true?

If so, is there any way to get around this? Other than creating a different logical volume for every placement? (which feels really inelegant)

Yes. When you create an LV with daughters, the placement of that daughter only happens once, and there’s a single pointer to that daughter PV. The placement of the mother uses the mother pointer, it does not make a mother copy, nor does it make daughter copies.

Doing the pointer traversal is the “correct” way to construct unique IDs for all of the instances of daughters. That’s exactly what the G4Touchable is – it’s the collected chain of nested placement pointers to get to the PV that occupies a given location in space.

Your concern about stale pointers is accurate. You shouldn’t cache pointers to touchables that you get from steps (a given touchable instance can be reused, repopulated with a different placement chain, at different stages in the tracking or the event loop), and you don’t want to cache the PV pointers either (they will very likely get reused in a subsequent run of the job if you change the geometry).

In my own framework, I have a function defined which takes a touchable argument, and assumes (yes, it checks :slight_smile: ) that the touchable is for one of my detector crystals. In it, I’ve hardcoded just enough of our geometry structure to know which levels of the touchble to interrogate to the the crystal copy number within a stack, then the stack copy number, and so on, which I can combine to produce a unique ID.

This function gets called and used for “every” hit in the SD, but it’s fairly efficient because it’s only doing pointer dereferencing, not any kind of tree search. I put “every” in quotes because I do cache the resulting unique ID for as long as the SD is seeing the same “current track.” Once it gets a step from a new track, it recomputes the unique ID.

All right, that makes sense to me.

Though I will say that what I meant by uncached was not quite the way you interpreted it. What you said made sense; but, what I was thinking when I said it was in reference to the processor cache.

Every step up the touchable history chain I go there is a greater chance that when the processor has to retrieve the data that is pointed to, it will have to do so from memory (~300 cycles) instead of finding it in the L1 (~3 cycles), L2 (~10 cycles), or L3 (~40-75 cycles) caches (the tyranny of the storage hierarchy.)

I understand that Geant4 is structured the way it is for flexibility, so pointer chasing that ends up missing the processor cache is pretty much inevitable. That said, I try to do my best to minimize it in the “hot inner loop” code that I have control over. I would need to profile a Geant4 run, but I would guess that processor caches make up a noticeable fraction of the time G4 spends running a simulation. Because during that miss, work on executing that thread grinds to a halt. Ordinarily the cache prefetcher will try to predict memory access and grab the next thing in time, but since these objects are not allocated in any regular even intervals, it almost certainly will not succeed.

On the other hand, declaring fresh logical volumes for each detector placement also can cause more cache misses due to the increased memory footprint and the larger number of geometry elements that need to be cached.

Nothing for it, I will have to try it both ways and profile things. Thanks for clarifying things!

Ah! Yes, what you say make a lot of sense. You’re quite right that we were using “cache” for entirely different things.

Unfortunately, you’re also right that G4 is not optimized in the way that you would like. At the same time, I’m pretty confident (from having gone through the exercise myself, fixing the Bertini model code) that G4 has many other sources of non-optimal performance than just pointer dereferencing.

I’m not sure there’s a way to work around this. The touchable itself is built by G4Navigator, based on the voxelized representation, not by traversing the tree.

You could, I think, write code in your GeometryConstruction::Construct() function to look up all of the “Detector” PVs, then navigate into them to get the daughter PV, and make yourself a lookup table matching the pairs of pointers with a unique ID. You’ll still have to query the two different levels of the touchable to get those pointers, but with the lookup table, you wouldn’t have to dereference them.