Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
53525d4
feat: add GPU wavelength shifting (WLS) physics
ggalgoczi Mar 24, 2026
38bd446
docs(readme): add WLS test instructions and update examples table
ggalgoczi Mar 24, 2026
c18b908
feat: add StandAloneGeant4Validation for CPU-only optical photon benc…
ggalgoczi Mar 26, 2026
d8c6647
feat: add DUNE example detector geometry for validation testing
ggalgoczi Mar 26, 2026
6a13733
feat: add GPU vs G4 hit comparison script
ggalgoczi Mar 26, 2026
b6b2281
move compare_gpu_g4.py to optiphy/ana
ggalgoczi Mar 26, 2026
a2826b1
feat: add WLS slab validation geometry and diagnostic script
ggalgoczi Mar 27, 2026
7485a28
move wls_diagnostic.py to optiphy/ana
ggalgoczi Mar 27, 2026
2fe0256
add photon-by-photon aligned comparison script
ggalgoczi Mar 28, 2026
f47598d
feat: add --aligned mode to StandAloneGeant4Validation
ggalgoczi Mar 28, 2026
08b8efc
simplify --aligned mode: drop U4Recorder/InstrumentedG4OpBoundaryProc…
ggalgoczi Mar 28, 2026
c7baeb4
add AlignedOpticalPhysics with ShimG4Op* for better RNILL alignment
ggalgoczi Mar 28, 2026
af5a65a
add standalone precooked curand sequence generator
ggalgoczi Mar 28, 2026
b1c0cdc
add chi2 and glancing-angle analysis to comparison script
ggalgoczi Mar 28, 2026
218e87c
add G4ValidationGenstep: electron primary for genstep validation
ggalgoczi Mar 29, 2026
e1f60fe
add savephotonhistory config flag and GPU/G4 hit .npy saving
ggalgoczi Mar 29, 2026
1de1408
add photon path visualization script with wavelength coloring
ggalgoczi Mar 31, 2026
159b657
add WLS validation test with dual-sphere geometry
ggalgoczi Mar 31, 2026
f0d47d9
add GPU vs G4 comparison script with simulation runner and plots
ggalgoczi Apr 1, 2026
9ea91f5
add step count distribution plot to GPU vs G4 comparison
ggalgoczi Apr 1, 2026
e5fe2d0
fix WLS time profile: use exponential instead of default delta
ggalgoczi Apr 2, 2026
2b0bcb7
update WLS test: proper arrival time KS test for shifted photons
ggalgoczi Apr 2, 2026
ee7652e
revert WLS time profile from GPURaytrace
ggalgoczi Apr 2, 2026
21850b5
simplify WLS test: remove redundant chi2, relax threshold to p>0.001
ggalgoczi Apr 2, 2026
c05a7f7
add WLS validation test to CI pipeline
ggalgoczi Apr 2, 2026
2df35f6
add DebugLite mode hint to run_and_compare output
ggalgoczi Apr 2, 2026
f9da1f2
allow OPTICKS_MAX_RECORD env var to override DebugLite default
ggalgoczi Apr 2, 2026
f1b37ae
apply clang-format (Microsoft style) to new and modified source files
ggalgoczi Apr 2, 2026
84994db
Merge branch 'main' into wavelength_shifting
ggalgoczi Apr 2, 2026
477d3f8
restore GPURaytrace electron energy to 5 GeV
ggalgoczi Apr 2, 2026
834b41c
fix clang-format style on changed lines in upstream files
ggalgoczi Apr 2, 2026
4c353f6
apply clang-format to sstandard.h, qsim.h, QSim.cc
ggalgoczi Apr 2, 2026
3c9744e
fix remaining clang-format linter issues
ggalgoczi Apr 2, 2026
5871107
remove blank line in GPURaytrace.cpp includes
ggalgoczi Apr 2, 2026
0a17c2d
Update qudarap/QSim.cc
ggalgoczi Apr 2, 2026
4210e06
Update src/GPURaytrace.cpp
ggalgoczi Apr 2, 2026
f661300
Update qudarap/qsim.h
ggalgoczi Apr 2, 2026
d5d43a0
fix scintillation genstep collection to include all time components
ggalgoczi Apr 4, 2026
f536bcc
remove duplicate OPTICKS_LOG.hh include in GPURaytrace.cpp
ggalgoczi Apr 4, 2026
e495dd7
rename det.gdml to apex.gdml and add benchmark script
ggalgoczi Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ jobs:
docker run --rm --gpus 'device=1' ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} tests/test_GPURaytrace.sh
docker run --rm --gpus 'device=1' ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} tests/test_GPUPhotonFileSource.sh
docker run --rm --gpus 'device=1' ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} tests/test_GPUPhotonSource_8x8SiPM.sh
docker run --rm --gpus 'device=1' ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} tests/test_wavelength_shifting.sh
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ EIC-Opticks provides several examples demonstrating GPU-accelerated optical phot
| `GPUPhotonSource` | Optical photons (torch) | Any GDML | G4 + GPU side-by-side validation |
| `GPUPhotonSourceMinimal` | Optical photons (torch) | Any GDML | GPU-only test |
| `GPUPhotonFileSource` | Optical photons (text file) | Any GDML | GPU-only, user-defined photons from file |
| WLS test | Wavelength shifting | WLS sphere + detector shell | Validate GPU WLS physics |

### Example 1: GPUCerenkov (Cerenkov Only)

Expand Down Expand Up @@ -297,6 +298,55 @@ GPUPhotonFileSource -g tests/geom/opticks_raindrop.gdml -p my_photons.txt -m run

**Source files:** `src/GPUPhotonFileSource.cpp`, `src/GPUPhotonFileSource.h`

### Example 6: Wavelength Shifting (WLS) Test

This test validates the GPU wavelength shifting implementation using a dedicated
geometry with a WLS sphere surrounded by a detector shell:

```
Geometry: wls_test.gdml
├── Air world (r=200 mm)
│ ├── WLS sphere (r=20 mm) ← Absorbs UV, re-emits visible
│ └── Glass detector shell (r=28-30 mm) ← 100% detection efficiency
```

The WLS material absorbs UV photons (350 nm) and re-emits them isotropically at
longer wavelengths (peak ~481 nm) with a 0.5 ns exponential time delay. The test
fires 1000 monochromatic 350 nm photons from the origin into the WLS sphere.

```bash
GPUPhotonSourceMinimal -g tests/geom/wls_test.gdml -c wls_test -m tests/run.mac -s 42
```

**Expected results:**
- ~990/1000 photons detected (10 absorbed after failing energy conservation)
- All hits wavelength-shifted from 350 nm to mean ~487 nm
- Energy conservation: no hits with wavelength < 350 nm
- Isotropic re-emission: mean momentum direction near zero
- Time delay: mean ~0.6 ns (propagation + 0.5 ns exponential WLS decay)

**GDML WLS properties required** (same syntax for G4 10.x and 11.x):
```xml
<define>
<matrix coldim="2" name="WLSABSLENGTH" values="1.77e-06 10000.0 ... 4.13e-06 0.01"/>
<matrix coldim="2" name="WLSCOMPONENT" values="1.77e-06 0.00 ... 3.10e-06 0.00"/>
<matrix coldim="1" name="WLSTIMECONSTANT" values="0.5"/>
</define>
<materials>
<material name="WLSMaterial">
<property name="WLSABSLENGTH" ref="WLSABSLENGTH"/>
<property name="WLSCOMPONENT" ref="WLSCOMPONENT"/>
<property name="WLSTIMECONSTANT" ref="WLSTIMECONSTANT"/>
</material>
</materials>
```

Unlike scintillation properties, WLS property names are the same in both Geant4
10.x and 11.x — no dual-naming is needed.

**Test files:** `tests/geom/wls_test.gdml`, `config/wls_test.json`
**Implementation docs:** `docs/WLS_IMPLEMENTATION.md`

### Torch configuration

`GPUPhotonSource` and `GPUPhotonSourceMinimal` read photon source parameters from a
Expand All @@ -317,6 +367,7 @@ JSON config file (default `config/dev.json`). Key fields:
|---------|-------------|-------------|-----------------|----------------------|---------------------|
| Cerenkov genstep collection | ✓ | ✓ | ✗ | ✗ | ✗ |
| Scintillation genstep collection | ✗ | ✓ | ✗ | ✗ | ✗ |
| Wavelength shifting (WLS) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Torch photon generation | ✗ | ✗ | ✓ | ✓ | ✗ |
| Photon input from text file | ✗ | ✗ | ✗ | ✗ | ✓ |
| G4 optical photon tracking | ✓ | ✓ | ✓ | ✗ | ✗ |
Expand Down
30 changes: 30 additions & 0 deletions config/wls_100k.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"torch": {
"gentype": "TORCH",
"trackid": 0,
"matline": 0,
"numphoton": 100000,

"pos": [0.0, 0.0, 0.0],
"time": 0.0,

"mom": [0.0, 0.0, 1.0],
"weight": 0.0,

"pol": [1.0, 0.0, 0.0],
"wavelength": 350.0,

"zenith": [0.0, 1.0],
"azimuth": [0.0, 1.0],

"radius": 0.0,
"distance": 0.0,
"mode": 255,
"type": "disc"
},

"event": {
"mode": "DebugLite",
"maxslot": 10000000
}
}
30 changes: 30 additions & 0 deletions config/wls_scatter_viz.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"torch": {
"gentype": "TORCH",
"trackid": 0,
"matline": 0,
"numphoton": 10000,

"pos": [0.0, 0.0, -25.0],
"time": 0.0,

"mom": [0.0, 0.0, 1.0],
"weight": 0.0,

"pol": [1.0, 0.0, 0.0],
"wavelength": 350.0,

"zenith": [0.0, 0.3],
"azimuth": [0.0, 1.0],

"radius": 0.0,
"distance": 0.0,
"mode": 255,
"type": "disc"
},

"event": {
"mode": "HitPhoton",
"maxslot": 100000
}
}
30 changes: 30 additions & 0 deletions config/wls_slab.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"torch": {
"gentype": "TORCH",
"trackid": 0,
"matline": 0,
"numphoton": 100000,

"pos": [0.0, 0.0, -50.0],
"time": 0.0,

"mom": [0.0, 0.0, 1.0],
"weight": 0.0,

"pol": [1.0, 0.0, 0.0],
"wavelength": 400.0,

"zenith": [0.0, 0.0],
"azimuth": [0.0, 1.0],

"radius": 0.0,
"distance": 0.0,
"mode": 255,
"type": "disc"
},

"event": {
"mode": "DebugLite",
"maxslot": 10000000
}
}
30 changes: 30 additions & 0 deletions config/wls_test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"torch": {
"gentype": "TORCH",
"trackid": 0,
"matline": 0,
"numphoton": 1000,

"pos": [0.0, 0.0, 0.0],
"time": 0.0,

"mom": [0.0, 0.0, 1.0],
"weight": 0.0,

"pol": [1.0, 0.0, 0.0],
"wavelength": 350.0,

"zenith": [0.0, 1.0],
"azimuth": [0.0, 1.0],

"radius": 0.0,
"distance": 0.0,
"mode": 255,
"type": "disc"
},

"event": {
"mode": "DebugLite",
"maxslot": 1000000
}
}
78 changes: 78 additions & 0 deletions examples/benchmark_apex.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# benchmark_apex.sh — Measure GPU vs G4 speedup on apex.gdml
#
# Usage:
# ./examples/benchmark_apex.sh

GDML="apex.gdml"
MACRO="tests/run.mac"
EPS="0.00001"
EPS0="0.0006"
OUTDIR="plots"
CONFIG="det_debug"

if [ ! -f "$GDML" ]; then
echo "ERROR: $GDML not found. Run from the eic-opticks root directory."
exit 1
fi

echo "=== apex.gdml Benchmark ==="
echo "eps=$EPS, eps0=$EPS0"
echo "Running..."

LOGFILE=$(mktemp /tmp/bench_XXXXXX.txt)
OPTICKS_MAX_BOUNCE=1000 \
OPTICKS_PROPAGATE_EPSILON=$EPS \
OPTICKS_PROPAGATE_EPSILON0=$EPS0 \
GPURaytrace -g "$GDML" -m "$MACRO" -c "$CONFIG" &> "$LOGFILE" || true

GPU_TIME=$(grep "Simulation time:" "$LOGFILE" | awk '{print $3}')
G4_LINE=$(grep "^ User=" "$LOGFILE" | tail -1)
G4_CPU=$(echo "$G4_LINE" | grep -oP 'User=\K[0-9.]+')
G4_WALL=$(echo "$G4_LINE" | grep -oP 'Real=\K[0-9.]+')
NPHOTONS=$(grep "NumCollected:" "$LOGFILE" | tail -1 | awk '{print $NF}')
GPU_HITS=$(grep "Opticks: NumHits:" "$LOGFILE" | awk '{print $NF}')
G4_HITS=$(grep "Geant4: NumHits:" "$LOGFILE" | awk '{print $NF}')

if [ -z "$GPU_TIME" ] || [ -z "$G4_CPU" ]; then
echo "ERROR: Could not parse timing from output"
tail -30 "$LOGFILE"
rm -f "$LOGFILE"
exit 1
fi

python3 -c "
gpu = float('$GPU_TIME')
g4_cpu = float('$G4_CPU')
g4_wall = float('$G4_WALL')
nphotons = int('$NPHOTONS')
gpu_hits = int('$GPU_HITS')
g4_hits = int('$G4_HITS')
hit_diff = (gpu_hits - g4_hits) / g4_hits * 100 if g4_hits > 0 else 0

print()
print(f'Photons: {nphotons:>10,}')
print(f'GPU sim time: {gpu:>10.4f} s')
print(f'G4 CPU time: {g4_cpu:>10.2f} s')
print(f'G4 wall time: {g4_wall:>10.2f} s')
print()
print(f'Speedup (CPU): {g4_cpu/gpu:>10.0f}x')
print(f'Speedup (wall): {g4_wall/gpu:>10.0f}x')
print()
print(f'GPU rate: {nphotons/gpu/1e6:>10.1f} M photons/s')
print(f'G4 rate: {nphotons/g4_cpu/1e3:>10.1f} k photons/s')
print()
print(f'GPU hits: {gpu_hits:>10}')
print(f'G4 hits: {g4_hits:>10}')
print(f'Hit diff: {hit_diff:>+9.1f}%')
"

rm -f "$LOGFILE"

# Generate comparison plots if hit files exist
if [ -f "gpu_hits.npy" ] && [ -f "g4_hits.npy" ]; then
echo ""
echo "=== Generating comparison plots ==="
python3 optiphy/ana/run_and_compare.py --gpu-hits gpu_hits.npy --g4-hits g4_hits.npy --outdir "$OUTDIR" 2>&1 | tail -15
echo "Plots saved to $OUTDIR/"
fi
Loading
Loading