From 9dfd66852ad03304952c1affe7d7b41933aa60b9 Mon Sep 17 00:00:00 2001 From: Gabor Galgoczi Date: Fri, 1 May 2026 12:19:14 +0000 Subject: [PATCH 1/4] fix(qudarap): floor WLS absorption step at float32 precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When wls_absorption_distance is below the float32 ulp at world coords (e.g. 100 nm absorption depth at 30 m world coord, ulp ~3.6 µm), the update p.pos += dist*mom rounds to a no-op and the photon stays on the boundary, causing BVH ambiguity on the next trace. Floor the step at 4 * ulp(p.pos) along the entering direction so the absorption position is unambiguously inside the absorbing material. For typical world coordinates the floor is ~14 µm at 30 m or 0.5 µm at 1 m - negligible vs typical WLS layer thickness, but resolves the BVH ambiguity that loses ~12% of photons in single-stage WLS at DUNE scale and ~1% in two-stage WLS at the same scale. Per-photon overhead: 3 fmaxf, 1 fabsf x3, 1 multiply per WLS absorption. Measured kernel-time impact: +6.2% on a 24M-photon DUNE-module event. Validation on tests/geom/nested_dune_module.gdml seeds 42-45 single 1 GeV e- events: - GPU/G4 hit ratio: 0.988 -> 1.0037 - arrival-time chi^2/ndf (0-200 ns @ 2 ns): 6.67 -> 1.06 - wavelength chi^2/ndf (380-540 nm @ 2 nm): 3.99 -> 1.80 --- qudarap/qsim.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/qudarap/qsim.h b/qudarap/qsim.h index a4b7143d67..6f34ffea59 100644 --- a/qudarap/qsim.h +++ b/qudarap/qsim.h @@ -783,8 +783,16 @@ inline QSIM_METHOD int qsim::propagate_to_boundary(unsigned& flag, RNG& rng, sct if (wls_wins && wls_absorption_distance <= distance_to_boundary) { // WLS ABSORPTION: photon absorbed by wavelength shifting material - p.time += wls_absorption_distance / group_velocity; - p.pos += wls_absorption_distance * (p.mom); + // + // Subprecision floor: when wls_absorption_distance is below float32 + // precision at world coords, p.pos += dist*mom rounds to no-op and the + // photon stays on the boundary, causing BVH ambiguity on the next trace. + // Force minimum step of 4 ulps along the entering direction. + const float pos_max = fmaxf(fmaxf(fabsf(p.pos.x), fabsf(p.pos.y)), fabsf(p.pos.z)); + const float min_step = pos_max * 4.7683716e-7f; // 4 * 2^-23 + const float eff_wls_distance = fmaxf(wls_absorption_distance, min_step); + p.time += eff_wls_distance / group_velocity; + p.pos += eff_wls_distance * (p.mom); // Sample re-emitted wavelength from WLS emission spectrum ICDF float u_wls_wl = curand_uniform(&rng); From e464824aa93718df92099ecb4d1640a35d81ca00 Mon Sep 17 00:00:00 2001 From: Gabor Galgoczi Date: Sat, 9 May 2026 15:38:24 +0000 Subject: [PATCH 2/4] test(geom): add nested_dune_module.gdml for PR validation Two-stage DUNE module (pTP -> bluewls -> SiPM): 60 x 13.5 x 13 m^3 inner LAr inside a 200 um pTP shell, a 6 mm bluewls acrylic shell, and a 1 mm outer LAr detector shell with SiPM skin. Full RINDEX, GROUPVEL, WLSCOMPONENT, WLSABSLENGTH matrices included for pTP and bluewlsacrylic; LAr scintillation uses the narrow-band Babicz emission spectrum. --- tests/geom/nested_dune_module.gdml | 175 +++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 tests/geom/nested_dune_module.gdml diff --git a/tests/geom/nested_dune_module.gdml b/tests/geom/nested_dune_module.gdml new file mode 100644 index 0000000000..a84a488939 --- /dev/null +++ b/tests/geom/nested_dune_module.gdml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 54f54ed3b473648ae13256801bfbb93e0f771191 Mon Sep 17 00:00:00 2001 From: Gabor Galgoczi Date: Sat, 9 May 2026 15:38:24 +0000 Subject: [PATCH 3/4] fix(qudarap): cap WLS step at half slab thickness The subprecision floor at min_step = 4 * ulp(p.pos) can exceed distance_to_boundary in geometries where the slab thickness along the trajectory is below 4 ulps of world coords (e.g. near-grazing entry into a 200 um pTP shell at decameter world coords: distance_to_boundary ~ 200 um / cos(theta) drops below 14 um at theta > 86 deg). Without a ceiling the floor pushes the photon past the exit boundary, re-creating the BVH ambiguity at the far face that the floor was added to avoid at the near face. Clamping at distance_to_boundary itself parks the photon exactly on the exit face, same ambiguity. Clamp at distance_to_boundary * 0.5f instead so the absorption point stays clear of both faces. Cost: +1 fminf + 1 *0.5f (free exponent decrement on FP32) per WLS absorption. --- qudarap/qsim.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/qudarap/qsim.h b/qudarap/qsim.h index 6f34ffea59..5b44b6b22d 100644 --- a/qudarap/qsim.h +++ b/qudarap/qsim.h @@ -784,13 +784,17 @@ inline QSIM_METHOD int qsim::propagate_to_boundary(unsigned& flag, RNG& rng, sct { // WLS ABSORPTION: photon absorbed by wavelength shifting material // - // Subprecision floor: when wls_absorption_distance is below float32 - // precision at world coords, p.pos += dist*mom rounds to no-op and the - // photon stays on the boundary, causing BVH ambiguity on the next trace. - // Force minimum step of 4 ulps along the entering direction. + // Subprecision floor + half-thickness ceiling: when wls_absorption_distance + // is below float32 precision at world coords, p.pos += dist*mom rounds to + // no-op and the photon stays on the entry boundary, causing BVH ambiguity + // on the next trace. Force minimum step of 4 ulps along the entering + // direction; cap at half of distance_to_boundary so the floor cannot push + // the photon onto (or past) the exit boundary, which would re-create the + // same ambiguity at the far face. const float pos_max = fmaxf(fmaxf(fabsf(p.pos.x), fabsf(p.pos.y)), fabsf(p.pos.z)); const float min_step = pos_max * 4.7683716e-7f; // 4 * 2^-23 - const float eff_wls_distance = fmaxf(wls_absorption_distance, min_step); + const float max_step = distance_to_boundary * 0.5f; + const float eff_wls_distance = fminf(fmaxf(wls_absorption_distance, min_step), max_step); p.time += eff_wls_distance / group_velocity; p.pos += eff_wls_distance * (p.mom); From 6c01b52b76f83c2b9016381f843d49269e0c42d8 Mon Sep 17 00:00:00 2001 From: Dmitri Smirnov Date: Fri, 15 May 2026 12:34:24 -0400 Subject: [PATCH 4/4] update tests/run.mac as suggested in https://github.com/BNLNPPS/simphony/pull/316#issuecomment-4442782759 --- tests/run.mac | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/run.mac b/tests/run.mac index e2ed9028b3..5baaa6908b 100644 --- a/tests/run.mac +++ b/tests/run.mac @@ -1,6 +1,8 @@ # Can be run in batch, without graphic # or interactively: Idle> /control/execute run1.mac # +/process/optical/scintillation/setStackPhotons false +/process/optical/cerenkov/setStackPhotons false /run/verbose 1 /run/initialize /run/beamOn 1