32.4. Complex Detector Arrays
Table of Contents
Understanding Complex Detector Arrays
Complex detector arrays appear in many real Geant4 applications, such as calorimeters, tracking systems, PET scanners, or segmented dose monitors. They go beyond a simple grid of identical copies and often combine many elements, different shapes, multiple materials, and different levels of hierarchy. In this chapter the focus is how to think about, design, and implement such arrays in Geant4, building on your knowledge of basic shapes, replicas, and parameterized volumes from earlier chapters.
The aim is not to describe every possible detector design, but to show common design patterns and practical techniques that help you keep your geometry manageable, efficient, and compatible with sensitive detectors and analysis.
From Simple Grids to Hierarchical Arrays
The simplest detector array is a regular grid of identical elements, for example a $10 \times 10$ array of small scintillator blocks. In Geant4 this can often be built with either replica volumes or parameterized volumes, aligned along one or more axes.
Complex arrays arise when you add one or more of the following complications.
Elements of different types or sizes within the same array.
Irregular or non‑Cartesian layouts, such as cylindrical rings, hexagonal tilings, or staggered rows.
Multiple hierarchical levels, for example modules composed of submodules, which in turn contain individual crystals.
Detector components that repeat with different orientations, such as tilted layers in a tracking detector.
Regions that require different materials or different production cuts.
The key idea is to introduce logical structure into your geometry. Instead of thinking of a single flat list of thousands of volumes, think in terms of building blocks that you can assemble and reuse. At each level you use the most appropriate Geant4 tool, such as placements, replicas, or parameterizations, and combine them into a hierarchy of logical and physical volumes.
Designing a Modular Geometry
Before you write C++ code, it is useful to design your detector array on paper or with a simple drawing. The goal is to identify natural modules and their repetition patterns.
For a complex array, you usually want at least three conceptual levels.
An individual detector element, for example one crystal, one silicon strip, or one fiber. This level defines the small solid, its material, and often a sensitive detector.
A module, which is a small group of elements arranged in a fixed pattern, for example a tile of $4 \times 4$ crystals, or a readout board with several strips. Modules often map nicely to electronics or mechanical structures in a real detector.
A global arrangement, where you place many modules to build the full detector, for example filling a ring, barrel, or large plane.
A clear hierarchy gives you two practical benefits. First, the geometry code becomes shorter and easier to read, since each level is defined in its own function or class. Second, assigning detector IDs and interpreting hit positions becomes more straightforward, because each level can contribute part of the index.
In C++ you typically reflect this modular structure by writing helper methods inside your DetectorConstruction or in separate helper classes. For example, you might have:
A method that constructs one tile and returns its logical volume.
A method that fills a module with parameterized or replicated tiles.
A method that arranges modules inside the world volume.
You are not forced to follow one fixed pattern, but a modular design will make later changes much easier. If, for instance, you change the number of crystals per module, you only modify the code that builds a module, not the entire detector.
Important design rule:
Keep geometry modular and hierarchical. Avoid writing long flat code that places thousands of individual detector elements one by one.
Choosing Between Replicas, Parameterization, and Manual Placement
In a complex array you rarely use a single placement technique everywhere. Instead you combine several.
Replicas are best for strictly regular, evenly spaced divisions along one axis, where every copy has the same size and material and you do not need arbitrary rotations. They are highly efficient and simple to use.
Parameterized volumes are more flexible. You write a parameterization class that defines size, position, material, and possibly rotation for each copy, based on the copy number. They are suited to irregular spacing, varying dimensions, or non‑Cartesian layouts while still using a single logical volume.
Manual placements with G4PVPlacement give you complete control at the cost of more code. You create each physical volume explicitly. This is practical for a small number of unique modules or for a few detector components with special orientations.
In a complex detector array, a common pattern is to combine these three methods across multiple levels. A typical design might be:
Use manual placements for a small number of major subsystems, such as an inner barrel, endcaps, and outer shielding.
Inside each subsystem, use replicas or parameterizations to fill large regions with repeated modules, such as tiles or bars.
Inside each module, use either replicas or manual placements to position a modest number of detector elements.
The choice is constrained by the features you need. For example, you cannot use replicas when you want each copy to have a different material or a nonuniform size. In that situation a parameterization is more appropriate. On the other hand, if you have a large perfectly regular grid of identical pixels, replicas will give better performance and simpler code.
Guideline:
Use replicas for strictly regular, equal divisions. Use parameterizations when positions or sizes vary. Use manual placements for a small number of unique or special volumes.
Implementing Multi‑Level Arrays
A multi‑level array is built by nesting logical volumes. At each level you define a mother logical volume and fill it with repeated child volumes.
Consider a detector where the final segmentation is a grid of small crystals arranged inside modules, and modules are arranged in a ring. A possible hierarchy is:
World volume.
Ring volume, a cylindrical or polygonal shape that holds multiple modules.
Module volume, a small box that holds a matrix of crystals.
Crystal volume, the smallest element, usually marked as a sensitive detector.
You might proceed as follows.
First define the crystal solid and logical volume, with its material.
Second define the module solid and logical volume. Inside this module you create a two‑dimensional array of crystals. To do that, you can either:
Use a 1D parameterization that computes two indices from the copy number, such as $i = \text{copy} \bmod N_x$, $j = \text{copy} / N_x$, then sets the position inside the module.
Or use nested replicas, one along $x$ and one along $y$, if the crystals perfectly divide the module dimensions.
Third define the ring solid and logical volume. Inside the ring, you place each module around a circle. This can be done with a parameterization that assigns an azimuthal angle and rotation to each copy, or by a loop over modules using G4PVPlacement with a rotation matrix for each position.
Finally place the ring volume inside the world volume.
The main idea is that the mother logical volume provides a local coordinate system for its children. If you place a module at some position in the ring, all crystals inside the module use positions relative to the module center, not to the world origin. This makes the geometry easy to reason about and simplifies transformations between global and local coordinates when you analyze hits.
Key concept:
Use nested logical volumes. Each level handles only its own pattern. Children are positioned in the coordinate system of their immediate mother volume, not in world coordinates.
Indexing and Detector IDs
In a complex array, each detector element needs a unique identifier so that you can map a hit to the correct element. It is not enough to know the volume name; you usually need indices such as module number, row, column, or ring and layer.
Geant4 automatically assigns a copy number to each physical volume. When you use replicas or parameterizations, you get a systematic mapping from copy number to position inside that structure. In a complex, multi‑level hierarchy you often combine several copy numbers to form a composite detector ID.
A common strategy is:
Assign each logical level a conceptually meaningful index. For example, the ring index, module index inside the ring, and crystal index inside the module.
In the sensitive detector or hit class, traverse the touchable history to retrieve copy numbers at several depths. For instance, you can use the pre step point touchable to get the copy number of the crystal volume, its mother module volume, and so on up the hierarchy.
Combine these indices to form a unique integer detector ID, or store them separately as fields in the hit or in an ntuple. For example, you can compute:
$$
\text{detID} = i_{\text{ring}} \times N_{\text{modules}} \times N_{\text{crystals}} + i_{\text{module}} \times N_{\text{crystals}} + i_{\text{crystal}}
$$
where the constants match your geometry structure.
You must decide in advance how many elements exist at each level, so that the index ranges do not overlap.
Alternatively, instead of compressing into a single integer, you can store a set of indices directly in the hit. For analysis, especially in ROOT, having separate columns for ring, module, and crystal often makes it easier to filter and project the data.
Important rule:
Define a clear mapping from volume copy numbers to detector indices at each level. Use the touchable history to build unique, meaningful detector IDs.
Handling Irregular and Non‑Cartesian Layouts
Many realistic arrays are not simple rectangular grids. You may encounter cylindrical rings of detectors, spherical shells, hexagonal tilings, or irregular arrangements that follow mechanical constraints. Replicas cannot describe such layouts; parameterization is the main tool.
In a parameterization, you write methods that receive a copy number and a reference to a physical volume. Your code sets the translation and rotation for that copy. For a ring of $N$ identical modules around a circle of radius $R$, a common pattern is:
Compute the angle as
$$
\phi = \frac{2\pi}{N} \times \text{copy}
$$
Set the translation to
$$
x = R \cos \phi, \quad y = R \sin \phi, \quad z = 0
$$
Define a rotation so that each module faces the center, typically a rotation around the $z$ axis by the same angle $\phi$.
Similarly, you can build cylindrical layers of detectors by parameterizing the radius, angle, and axial coordinate. For hexagonal tilings, you map the copy number to a pair of grid indices and then transform them into $x$ and $y$ coordinates following the hexagonal lattice.
The essential point is that the parameterization defines a mathematical mapping
$$
\text{copy number} \to (\text{position}, \text{rotation}, \text{size}, \text{material})
$$
You are free to define any mapping you like, as long as it is consistent with the total number of copies you pass when you build the parameterized volume.
When designing irregular arrays, ensure that volumes do not overlap. Geant4 provides overlap checking tools, and you should enable them during development. Irregular geometric patterns are more prone to small overlaps caused by rounding or trigonometric approximations. If necessary, you can introduce small gaps between elements by slightly reducing dimensions or increasing spacing.
Practical tip:
For non‑Cartesian arrays, implement a clean mathematical mapping from copy number to position and rotation in your parameterization. Always check for overlaps, especially when using trigonometric placements.
Balancing Detail, Performance, and Memory
Complex detector arrays can easily contain tens or hundreds of thousands of volumes. This level of detail can affect both simulation speed and memory usage. When you design the geometry, you must decide how much detail is really necessary for your physics goals.
Some aspects to balance are:
Number of volumes. Every physical volume and boundary crossing has a cost. Use replicas and parameterizations rather than many individual placements when you have large regular structures.
Granularity of segmentation. Finer segmentation yields more detailed spatial information but more hits, more data to process, and longer simulation times. Choose the smallest detector element size that still resolves the effects you need to study.
Level of mechanical detail. Many mechanical features, such as screws or small support structures, have little impact on the physics of interest. Simplifying them, or representing them with averaged materials, can dramatically simplify the geometry without affecting results significantly.
Grouping of inactive structures. Instead of modeling every inactive component separately, you can combine them into larger volumes with effective materials or treat them as a small uniform layer of material.
In Geant4, performance is not only determined by the number of volumes, but also by how the navigator traverses the geometry. Large hierarchies and deeply nested volumes are normally handled efficiently, but extreme complexity can still slow things down. Whenever you introduce a new level of detail, test the impact on runtime using a simple primary source, and consider stripping unnecessary detail if performance becomes an issue.
You can also use production cuts and region definitions to limit the generation and tracking of secondary particles in parts of the detector that are not of primary interest. This is particularly valuable in very large arrays that contain both a fine segmented active region and a large passive support region.
Performance guideline:
Only introduce as much segmentation and mechanical detail as you need for your physics goals. Too many volumes or overly fine segmentation can severely slow down simulations.
Integrating Sensitive Detectors with Complex Arrays
Ultimately, complex detector arrays exist to record detailed information about particle interactions. Integrating sensitive detectors correctly is essential.
At the lowest level of your hierarchy, you identify the logical volume that represents the active detector element. You attach a sensitive detector object to this logical volume. Geant4 then creates hits for every copy of that logical volume, using the copy number and touchable history to distinguish them.
When you design complex arrays, you must ensure that:
Only the intended active volumes have sensitive detectors attached. If you accidentally assign a sensitive detector to an enclosing module volume, you may receive aggregated hits that are hard to interpret.
Each active volume has a well defined and nonoverlapping placement. If multiple volumes occupy the same space or if boundaries are ambiguous, you may see missing energy deposition or confusing hit patterns.
Your hit class stores enough information to identify the element in terms meaningful to your analysis, for example ring, module, and crystal indices, along with energy and time. This ties back to the indexing scheme discussed earlier.
In complex systems, especially those with multiple types of detector elements, you may define several sensitive detectors and assign each to its corresponding logical volume type. For instance, you might have one sensitive detector class for scintillator crystals and another for tracking strips, each with its own hit class optimized for the data it needs to record.
To keep your code manageable, it is useful to centralize the creation and registration of sensitive detectors in a dedicated method. This makes it clear which logical volumes are active and helps avoid accidental duplication or omission when you modify the geometry.
Crucial point:
Attach sensitive detectors at the correct lowest‑level logical volumes, and ensure your hit data structure can reconstruct the full detector element identity in a complex array.
Testing and Debugging Complex Arrays
Complex detector arrays are more prone to subtle geometry issues than simple setups. It is crucial to spend time verifying that the geometry is built as intended before relying on simulation results.
Visualization is often the first tool. Use Geant4 visualization to draw your geometry, zoom into specific regions, and draw wireframes of individual subsystems or logical volumes. For dense arrays, it can help to restrict drawing to a subset of volumes or to use transparency so you can see internal elements.
Overlap checking is especially important. For each mother volume that contains a complex arrangement, enable overlap checks when you place the children. You can also use global overlap check commands in macros. If overlaps are reported, investigate the specific volumes and adjust dimensions or spacing.
Simple test runs can validate that your indexing and sensitive detector setup works. For example, you can:
Shoot a small number of particles at specific regions of the detector and print information from your sensitive detector about which elements recorded hits.
Use macro commands to move or narrow the beam so that it only covers one module or a small group of elements, then verify that only the expected indices appear in the hit output.
Record a small set of events into an ntuple and inspect the distribution of module and element IDs. Gaps or impossible values typically indicate a problem in the mapping between volume copy numbers and detector indices.
As your arrays grow more complex, build and verify them incrementally. Start by constructing a single module and testing it. Then place a small number of modules in the global geometry and check again. Only once everything works should you scale up to the full detector.
Views: 9
KAHIBARO