Title
Tubs volume transmits ~25% fewer photons than an area-matched Hexagon volume under normal-incidence parallel-beam illumination
Environment
- OpenGATE version: 10.1.0
- OpenGATE install path:
/home/gopalsurya2015/gate_env/lib/python3.10/site-packages/opengate/__init__.py
- Python version: 3.10.12
- OS:
Linux gopalgo-v1 6.8.0-1063-gcp #69~22.04.1-Ubuntu SMP Tue Jun 30 15:12:15 UTC 2026 x86_64 GNU/Linux
- Geant4 version: not determined (see note below)
Summary
A Tubs volume (cylindrical hole) and a Hexagon volume (hexagonal
hole) with exactly matched cross-sectional area (0.5261 mm² each)
transmit meaningfully different fractions of a normal-incidence
parallel photon beam fired straight through them. For photons
travelling parallel to a hole's axis, transmission fraction should
depend only on open cross-sectional area, not hole shape — basic
solid geometry, independently confirmed by a from-scratch geometric
ray tracer built with no Geant4/OpenGATE dependency at all (see below).
OpenGATE's simulated result diverges from this by ~25%, consistently,
across 9 independent variations of the test.
Minimal reproduction
Attached: opengate_tubs_hexagon_bug_repro.py — self-contained,
depends only on opengate, scipy, uproot. Builds a single hole
(no repeated/parameterised lattice) drilled through a small tungsten
(G4_W) block, fires 2,000,000 monoenergetic 140 keV photons in a
perfectly parallel beam straight through it, and counts raw hits on a
downstream CdTe detector.
Latest run output:
Hex hole area: 0.5261 mm^2
Cylinder hole area: 0.5261 mm^2 (ratio: 1.0000)
hexagon: 213863 raw hits / 2,000,000 primaries = 10.693% transmission
cylinder: 160347 raw hits / 2,000,000 primaries = 8.017% transmission
Cylinder/Hexagon transmission ratio: 0.750
Expected ratio: ~1.0 (matched area, normal incidence, no physics
reason for the two shapes to differ). Observed: consistently ~0.73-0.75
across every test variation described below.
Investigation already performed
This ratio was reproduced across 9 independent, purpose-built
isolation tests, each targeting one specific candidate explanation:
- Full 56,834-hole lattice, full physics (real tungsten density,
normal Compton/photoelectric physics): 36.79% (hex) vs 26.97%
(cylinder), ratio 0.733.
- Same lattice, deterministic-blocking material (density scaled
~100x normal, to make septa transmission ~e^-1600 ≈ 0 regardless
of angle) — isolates geometry from photon physics: 38.02% (hex)
vs 27.88% (cylinder), ratio 0.733. Gap unchanged with photon
physics effectively removed — rules out Compton scatter, septal
penetration, and photoelectric absorption as the cause.
- Hole area measured directly via pixel-mask connected-component
analysis on the transmission image: exact match, 0.5261 mm² for
both shapes (ratio 1.000) — rules out a hole-sizing bug.
- Hole count and nearest-neighbor spacing measured directly (not
assumed) from the same images: 22 holes each, spacing within
0.15% of each other and within 0.8% of the pitch construction's
theoretical value (1.27mm) — rules out a placement/pitch bug.
- Independent geometric ray tracer (pure Python/numpy, zero
Geant4/OpenGATE dependency) replicating the exact same lattice
(pitch, offset_nb=2 interleaving, hole areas) via Monte Carlo point
sampling, testing 5 different hexagon rotational orientations:
all converge to ~36%, matching EACH OTHER and matching the
analytic open-area prediction — the gap does not appear at all
in an independent model of the same geometry, ruling out
orientation-dependent hexagon packing efficiency as an explanation.
- Exact constructor parameters read back from the live Geant4
objects (not just what was requested) immediately before solid
construction: hole.radius/hole.height (hex) and
hole.rmax/hole.rmin/hole.dz/hole.sphi/hole.dphi
(cylinder) all matched intended values exactly, including the
confirmed-correct half-length convention for Tubs.dz.
- Single hole, no
RepeatParametrisedVolume (this reproduction
case): ratio 0.749 — matches the full-lattice ratio (0.733)
closely, ruling out the repeat/parameterisation mechanism as the
cause.
- Rotation transform chain eliminated entirely, by redefining
the beam axis so neither the mother block nor the hole needs any
rotation applied (both left at Geant4's default identity): ratio
0.751 — unchanged from the rotated case (0.749). Also separately
verified via scipy.spatial.transform.Rotation that the composed
rotation in the rotated version was mathematically exact (hole
axis lands at world [0,1,0] to floating-point precision).
- Boundary-grazing at exact axial incidence tested via a tiny
0.01° beam tilt off exact [0,0,1] (negligible ~0.002mm lateral
drift over the 14mm hole depth): ratio 0.750 — unchanged.
Simulation time did increase substantially with the tilt (46.7s →
170.8s for the cylinder case), but this timing change was NOT
accompanied by any change in the transmission ratio, suggesting
the timing and transmission effects are decoupled, not causally
linked.
We also read G4Tubs::Inside(), both DistanceToIn() overloads, and
both DistanceToOut() overloads directly from the Geant4 source, and
confirmed all are purely analytic closed-form quadratic solutions —
no facetization, faceting parameter, or voxel-mesh approximation
anywhere in the navigation-relevant code paths (the only
mesh/vertices-related code, CreateRotatedVertices(), is used solely
for bounding-box/visualization purposes and is never called by the
navigation methods above).
What we're asking
We've exhausted what's diagnosable by reading source and building
isolation tests from the application side. Given the above, the
remaining discrepancy appears to be something in the interaction
between OpenGATE's Tubs wrapper and Geant4's G4Tubs/navigation
machinery that we haven't been able to identify without step-through
debugging access. Any guidance on:
- whether this is a known issue,
- whether there's a debug/verbose mode that would show per-step
navigation decisions for a Tubs daughter volume, or
- anything else in the OpenGATE→Geant4 volume-placement path worth
checking
would be very much appreciated.
opengate_tubs_hexagon_bug_repro.py
Title
Tubs volume transmits ~25% fewer photons than an area-matched Hexagon volume under normal-incidence parallel-beam illumination
Environment
/home/gopalsurya2015/gate_env/lib/python3.10/site-packages/opengate/__init__.pyLinux gopalgo-v1 6.8.0-1063-gcp #69~22.04.1-Ubuntu SMP Tue Jun 30 15:12:15 UTC 2026 x86_64 GNU/LinuxSummary
A
Tubsvolume (cylindrical hole) and aHexagonvolume (hexagonalhole) with exactly matched cross-sectional area (0.5261 mm² each)
transmit meaningfully different fractions of a normal-incidence
parallel photon beam fired straight through them. For photons
travelling parallel to a hole's axis, transmission fraction should
depend only on open cross-sectional area, not hole shape — basic
solid geometry, independently confirmed by a from-scratch geometric
ray tracer built with no Geant4/OpenGATE dependency at all (see below).
OpenGATE's simulated result diverges from this by ~25%, consistently,
across 9 independent variations of the test.
Minimal reproduction
Attached:
opengate_tubs_hexagon_bug_repro.py— self-contained,depends only on
opengate,scipy,uproot. Builds a single hole(no repeated/parameterised lattice) drilled through a small tungsten
(
G4_W) block, fires 2,000,000 monoenergetic 140 keV photons in aperfectly parallel beam straight through it, and counts raw hits on a
downstream CdTe detector.
Latest run output:
Expected ratio: ~1.0 (matched area, normal incidence, no physics
reason for the two shapes to differ). Observed: consistently ~0.73-0.75
across every test variation described below.
Investigation already performed
This ratio was reproduced across 9 independent, purpose-built
isolation tests, each targeting one specific candidate explanation:
normal Compton/photoelectric physics): 36.79% (hex) vs 26.97%
(cylinder), ratio 0.733.
~100x normal, to make septa transmission ~e^-1600 ≈ 0 regardless
of angle) — isolates geometry from photon physics: 38.02% (hex)
vs 27.88% (cylinder), ratio 0.733. Gap unchanged with photon
physics effectively removed — rules out Compton scatter, septal
penetration, and photoelectric absorption as the cause.
analysis on the transmission image: exact match, 0.5261 mm² for
both shapes (ratio 1.000) — rules out a hole-sizing bug.
assumed) from the same images: 22 holes each, spacing within
0.15% of each other and within 0.8% of the pitch construction's
theoretical value (1.27mm) — rules out a placement/pitch bug.
Geant4/OpenGATE dependency) replicating the exact same lattice
(pitch, offset_nb=2 interleaving, hole areas) via Monte Carlo point
sampling, testing 5 different hexagon rotational orientations:
all converge to ~36%, matching EACH OTHER and matching the
analytic open-area prediction — the gap does not appear at all
in an independent model of the same geometry, ruling out
orientation-dependent hexagon packing efficiency as an explanation.
objects (not just what was requested) immediately before solid
construction:
hole.radius/hole.height(hex) andhole.rmax/hole.rmin/hole.dz/hole.sphi/hole.dphi(cylinder) all matched intended values exactly, including the
confirmed-correct half-length convention for
Tubs.dz.RepeatParametrisedVolume(this reproductioncase): ratio 0.749 — matches the full-lattice ratio (0.733)
closely, ruling out the repeat/parameterisation mechanism as the
cause.
the beam axis so neither the mother block nor the hole needs any
rotation applied (both left at Geant4's default identity): ratio
0.751 — unchanged from the rotated case (0.749). Also separately
verified via
scipy.spatial.transform.Rotationthat the composedrotation in the rotated version was mathematically exact (hole
axis lands at world [0,1,0] to floating-point precision).
0.01° beam tilt off exact [0,0,1] (negligible ~0.002mm lateral
drift over the 14mm hole depth): ratio 0.750 — unchanged.
Simulation time did increase substantially with the tilt (46.7s →
170.8s for the cylinder case), but this timing change was NOT
accompanied by any change in the transmission ratio, suggesting
the timing and transmission effects are decoupled, not causally
linked.
We also read
G4Tubs::Inside(), bothDistanceToIn()overloads, andboth
DistanceToOut()overloads directly from the Geant4 source, andconfirmed all are purely analytic closed-form quadratic solutions —
no facetization, faceting parameter, or voxel-mesh approximation
anywhere in the navigation-relevant code paths (the only
mesh/vertices-related code,
CreateRotatedVertices(), is used solelyfor bounding-box/visualization purposes and is never called by the
navigation methods above).
What we're asking
We've exhausted what's diagnosable by reading source and building
isolation tests from the application side. Given the above, the
remaining discrepancy appears to be something in the interaction
between OpenGATE's
Tubswrapper and Geant4'sG4Tubs/navigationmachinery that we haven't been able to identify without step-through
debugging access. Any guidance on:
navigation decisions for a
Tubsdaughter volume, orchecking
would be very much appreciated.
opengate_tubs_hexagon_bug_repro.py