From 3378154270e4fd1221f3e159157a8207180768ff Mon Sep 17 00:00:00 2001 From: Dmitri Smirnov Date: Wed, 29 Jul 2026 21:33:39 +0000 Subject: [PATCH 1/2] test(simg4ox): validate dRICH sensor hits The partial-phi sphere and tube implementation from BNLNPPS/simphony#426 has focused geometry and intersection tests, but it lacks an end-to-end regression using a realistic detector that converts those solids and compares optical-photon detection across Geant4 and Opticks. The dRICH fixture provides that coverage with one partial sphere, three partial tubes, and 210 annotated photon-sensor volumes. Add a deterministic 10,000-photon, 420 nm torch configuration aimed at a dRICH SiPM patch so the CPU and GPU receive identical optical input and produce stable nonzero hit samples. Register Integration.simg4ox.drich through the common CMake helper, allow hit-validation cases to select a custom macro, and use run_validate.mac so Geant4 invokes the sensor SDs. The complete geometry is converted before transport, so the test exercises partial-phi construction even though the controlled beam targets the sensors directly. GDML import strips pointer-like suffixes from logical-volume names, leaving distinct dRICH sensor volumes with duplicate names. Append a numeric index to each PhotonSD name so G4SDManager collection keys remain unique and all Geant4 sensor hits are retained for the existing three-sigma count and distribution comparison. --- config/drich.json | 34 ++++++++++++++++++++++++++++++++++ src/g4app.h | 4 +++- tests/CMakeLists.txt | 1 + tests/test_simg4ox.sh | 11 ++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 config/drich.json diff --git a/config/drich.json b/config/drich.json new file mode 100644 index 000000000..2d2aff4ff --- /dev/null +++ b/config/drich.json @@ -0,0 +1,34 @@ +{ + "torch": { + "gentype": "TORCH", + "trackid": 0, + "matline": 0, + "numphoton": 10000, + + "pos": [776.5, 402.6, 2590.0], + "time": 0.0, + + "mom": [0.7621, 0.4400, -0.4750], + "weight": 0.0, + + "pol": [1.0, 0.0, 0.0], + "wavelength": 420.0, + + "zenith": [0.0, 1.0], + "azimuth": [0.0, 1.0], + + "radius": 10.0, + "distance": 0.0, + "mode": 255, + "type": "disc" + }, + + "event": { + "mode": "HitPhoton", + "maxslot": 1000000, + "max_bounce": 31, + "propagate_epsilon": 0.05, + "propagate_epsilon0": 0.05, + "propagate_epsilon0_mask": "TO,CK,SI,SC,RE" + } +} diff --git a/src/g4app.h b/src/g4app.h index faf30feb5..69dc3ee7a 100644 --- a/src/g4app.h +++ b/src/g4app.h @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -150,6 +151,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction void ConstructSDandField() override { G4SDManager* SDman = G4SDManager::GetSDMpointer(); + size_t sensitive_index = 0; const G4GDMLAuxMapType* auxmap = parser_.GetAuxMap(); for (auto const& [logVol, listType] : *auxmap) @@ -159,7 +161,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction if (auxtype.type == "SensDet") { G4cout << "DetectorConstruction::ConstructSDandField: Attach sensitive detector to logical volume: " << logVol->GetName() << G4endl; - G4String name = logVol->GetName() + "_PhotonDetector"; + G4String name = logVol->GetName() + "_" + std::to_string(sensitive_index++) + "_PhotonDetector"; PhotonSD* aPhotonSD = new PhotonSD(name); SDman->AddNewDetector(aPhotonSD); logVol->SetSensitiveDetector(aPhotonSD); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab4a1205d..6ad1f41ef 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ add_simg4ox_integration_test(raindrop) add_simg4ox_integration_test(dune_mock_wls_detector_box) add_simg4ox_integration_test(8x8SiPM_w_CSI_optial_grease) add_simg4ox_integration_test(opticks_two_spheres) +add_simg4ox_integration_test(drich) set(SIMPHONY_SIMG4OX_MULTIEVENT_TEST_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}/simg4ox_multievent") file(MAKE_DIRECTORY "${SIMPHONY_SIMG4OX_MULTIEVENT_TEST_WORKDIR}") diff --git a/tests/test_simg4ox.sh b/tests/test_simg4ox.sh index 32c1d5c84..648a51fed 100755 --- a/tests/test_simg4ox.sh +++ b/tests/test_simg4ox.sh @@ -21,6 +21,7 @@ usage() { echo " dune_mock_wls_detector_box" >&2 echo " 8x8SiPM_w_CSI_optial_grease" >&2 echo " opticks_two_spheres" >&2 + echo " drich" >&2 } if [[ $# -gt 1 ]]; then @@ -50,6 +51,7 @@ run_record_validation() { run_hit_validation() { local geometry=$1 local config_name=$2 + local macro_file=${3:-${MAC_FILE}} local run_log="${PWD}/simg4ox.log" rm -f "${PWD}/g_hits.npy" "${PWD}/s_hits.npy" "${run_log}" @@ -58,7 +60,7 @@ run_hit_validation() { "${SIMG4OX_BIN}" \ -g "${REPO_DIR}/tests/geom/${geometry}.gdml" \ -c "${config_name}" \ - -m "${MAC_FILE}" \ + -m "${macro_file}" \ -s "${SEED}" > "${run_log}" 2>&1 "${PYTHON}" "${COMPARE_AB}" hits "${PWD}/g_hits.npy" "${PWD}/s_hits.npy" \ @@ -82,6 +84,13 @@ case "${TEST_CASE}" in # regression where each PhotonSD overwrites g_hits.npy with its own collection. run_hit_validation opticks_two_spheres dev ;; + drich) + # Loading the full geometry exercises conversion of its partial-phi + # sphere and tube solids. The torch aims identical optical photons at + # a SiPM patch for a stable, nonzero CPU/GPU hit comparison. + # Invoke Geant4 SDs for the EFFICIENCY=1 SiPM skin surfaces. + run_hit_validation drich drich "${REPO_DIR}/tests/run_validate.mac" + ;; *) echo "Unknown simg4ox test case: ${TEST_CASE}" >&2 usage From 63129e70c78c0c9580628fb80db9b2a8f0dcd63d Mon Sep 17 00:00:00 2001 From: Dmitri Smirnov Date: Thu, 30 Jul 2026 15:31:10 +0000 Subject: [PATCH 2/2] fix(g4): ensure unique PhotonSD names across apps --- src/GPUCerenkov.h | 4 +++- src/GPURaytrace.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/GPUCerenkov.h b/src/GPUCerenkov.h index 40c417939..d9cb1a46c 100644 --- a/src/GPUCerenkov.h +++ b/src/GPUCerenkov.h @@ -1,6 +1,7 @@ #include #include #include +#include #include "G4AutoLock.hh" #include "G4BooleanSolid.hh" @@ -263,6 +264,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction { G4cout << "ConstructSDandField is called." << G4endl; G4SDManager *SDman = G4SDManager::GetSDMpointer(); + size_t sensitive_index = 0; const G4GDMLAuxMapType *auxmap = parser_.GetAuxMap(); for (auto const &[logVol, listType] : *auxmap) @@ -272,7 +274,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction if (auxtype.type == "SensDet") { G4cout << "Attaching sensitive detector to logical volume: " << logVol->GetName() << G4endl; - G4String name = logVol->GetName() + "_PhotonDetector"; + G4String name = logVol->GetName() + "_" + std::to_string(sensitive_index++) + "_PhotonDetector"; PhotonSD *aPhotonSD = new PhotonSD(name); SDman->AddNewDetector(aPhotonSD); logVol->SetSensitiveDetector(aPhotonSD); diff --git a/src/GPURaytrace.h b/src/GPURaytrace.h index 14606bfe3..03308240f 100644 --- a/src/GPURaytrace.h +++ b/src/GPURaytrace.h @@ -1,6 +1,7 @@ #include #include #include +#include #include "G4AutoLock.hh" #include "G4BooleanSolid.hh" @@ -264,6 +265,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction { G4cout << "ConstructSDandField is called." << G4endl; G4SDManager *SDman = G4SDManager::GetSDMpointer(); + size_t sensitive_index = 0; const G4GDMLAuxMapType *auxmap = parser_.GetAuxMap(); for (auto const &[logVol, listType] : *auxmap) @@ -273,7 +275,7 @@ struct DetectorConstruction : G4VUserDetectorConstruction if (auxtype.type == "SensDet") { G4cout << "Attaching sensitive detector to logical volume: " << logVol->GetName() << G4endl; - G4String name = logVol->GetName() + "_PhotonDetector"; + G4String name = logVol->GetName() + "_" + std::to_string(sensitive_index++) + "_PhotonDetector"; PhotonSD *aPhotonSD = new PhotonSD(name); SDman->AddNewDetector(aPhotonSD); logVol->SetSensitiveDetector(aPhotonSD);