Back to home page

EIC code displayed by LXR

 
 

    


Warning, /eic-opticks/docs/inputs-outputs.md is written in an unsupported language. File is not indexed.

0001 # Simulation inputs and outputs
0002 
0003 This guide collects the runtime configuration, application entry points, and
0004 GDML requirements that were previously documented in the top-level README. It
0005 also defines the persisted optical-event output protocol for Python readers.
0006 For the optical-surface physics implemented by Simphony, see
0007 [Physics](physics.md).
0008 
0009 ## Geometry input
0010 
0011 Simphony imports detector geometry in GDML format. Example applications accept
0012 GDML files through `-g`, for example:
0013 
0014 ```bash
0015 GPUCerenkov -g tests/geom/opticks_raindrop.gdml
0016 ```
0017 
0018 The GDML must define the optical properties needed by the GPU path:
0019 
0020 - Efficiency, used to specify detection efficiency and assign sensitive
0021   surfaces
0022 - Material refractive index, used for optical boundary transmission and
0023   reflection
0024 - Material group velocity
0025 - Reflectivity, used for non-sensor specular or diffuse explicit surfaces
0026 - Additional Geant4 material and surface optical properties needed for
0027   CPU-side Geant4 validation, noting that not all of them are used by
0028   Simphony's standard GPU propagation
0029 
0030 See [Physics](physics.md) for the current standard-GPU interpretation of GDML
0031 optical-surface properties such as `model`, `finish`, `type`, `EFFICIENCY`,
0032 and `REFLECTIVITY`.
0033 
0034 ## Photon source inputs
0035 
0036 Simphony examples feed the GPU simulation in three common ways:
0037 
0038 - Charged-particle events in Geant4 collect Cerenkov and, where supported,
0039   scintillation gensteps. `GPUCerenkov` and `GPURaytrace` use this path.
0040 - Torch-source applications generate optical photons directly from the JSON
0041   configuration described below.
0042 
0043 The [examples guide](../examples/README.md#application-capabilities) compares
0044 the executable-level capabilities, including whether Geant4 optical-photon
0045 tracking is also run for validation.
0046 
0047 ## Torch configuration
0048 
0049 `simg4ox` reads photon source parameters from a JSON config file, by default `config/dev.json`.
0050 
0051 | Field | Description |
0052 |-------|-------------|
0053 | `type` | Source shape: `disc`, `sphere`, `point` |
0054 | `radius` | Size of the source area (mm) |
0055 | `pos` | Center position `[x, y, z]` (mm) |
0056 | `mom` | Emission direction `[x, y, z]` (normalized automatically) |
0057 | `numphoton` | Number of photons to generate |
0058 | `wavelength` | Photon wavelength (nm) |
0059 
0060 ## Defining primary particles
0061 
0062 For charged-particle examples, the user or developer defines the primary
0063 particle count and Geant4 threading in the macro file, and the primary
0064 particle position, momentum, and related settings in the application's
0065 `GeneratePrimaries` implementation. `src/GPUCerenkov.h` provides a compact
0066 working example.
0067 
0068 ```text
0069 /run/numberOfThreads {threads}
0070 /run/verbose 1
0071 /process/optical/cerenkov/setStackPhotons {flag}
0072 /run/initialize
0073 /run/beamOn 500
0074 ```
0075 
0076 `setStackPhotons` controls whether Geant4 propagates optical photons. In
0077 production-style GPU runs, Simphony handles optical photon propagation on the
0078 GPU. If more photons are simulated than can fit in GPU memory, move the GPU
0079 simulation and hit retrieval from run-end handling to per-event handling.
0080 
0081 ## GDML scintillation properties
0082 
0083 For scintillation to work with both Geant4 11.x and Simphony GPU simulation,
0084 the GDML must define properties using the correct syntax.
0085 
0086 1. Const properties, such as yield and time constants, must use
0087    `coldim="1"` matrices:
0088 
0089 ```xml
0090 <define>
0091     <matrix coldim="1" name="SCINT_YIELD" values="5000.0"/>
0092     <matrix coldim="1" name="FAST_TIME_CONST" values="21.5"/>
0093 </define>
0094 ```
0095 
0096 2. Both old and new style property names are required for Simphony
0097    compatibility:
0098 
0099 ```xml
0100 <material name="Crystal">
0101     <!-- New Geant4 11.x names -->
0102     <property name="SCINTILLATIONYIELD" ref="SCINT_YIELD"/>
0103     <property name="SCINTILLATIONCOMPONENT1" ref="SCINT_SPECTRUM"/>
0104     <property name="SCINTILLATIONTIMECONSTANT1" ref="FAST_TIME_CONST"/>
0105     <!-- Old-style names for Opticks U4Scint -->
0106     <property name="FASTCOMPONENT" ref="SCINT_SPECTRUM"/>
0107     <property name="SLOWCOMPONENT" ref="SCINT_SPECTRUM"/>
0108     <property name="REEMISSIONPROB" ref="REEMISSION_PROB"/>
0109 </material>
0110 ```
0111 
0112 See `tests/geom/8x8SiPM_w_CSI_optial_grease.gdml` for a complete working
0113 example.
0114 
0115 ## Output protocol
0116 
0117 Simphony persists optical-event arrays as `.npy` files using component names
0118 from `sysrap/SComp.h`. The canonical full step-history filename is
0119 `record.npy`.
0120 
0121 The schema below is source-backed by `SEvt::makePhoton`, `SEvt::makeRecord`,
0122 `SEvt::makeSeq`, `SEvt::gatherHit`, `SEvt::gatherDomain`, `sphoton`, `sseq`,
0123 and `OpticksPhoton.h`. Treat the file stems, dtypes, array ranks, `sphoton`
0124 quad layout, `q3` bit packing, and `seq.npy` nibble encoding as the reader
0125 contract. Event folder names and which files appear are runtime configuration.
0126 Auxiliary debug arrays such as `aux.npy`, `prd.npy`, `tag.npy`, `flat.npy`,
0127 and `sup.npy` are lower-level diagnostics.
0128 
0129 ### Event directory
0130 
0131 For applications that construct `simphony::Config`, the event output base is
0132 `Config::output_dir`, read from the JSON key `event.output_dir` and applied to
0133 the lower-level event system by `Config::Apply`. With the default event
0134 relative directory, GPU event arrays are written below:
0135 
0136 ```text
0137 <Config.output_dir>/ALL0_no_opticks_event_name/A000/
0138 ```
0139 
0140 The path is assembled as:
0141 
0142 | Segment | Source | Notes |
0143 |---------|--------|-------|
0144 | `<Config.output_dir>` | `Config::output_dir`, JSON `event.output_dir` | The app-level base directory for event output folders. The default JSON configs currently use `./`. |
0145 | `ALL0_no_opticks_event_name` | lower-level event relative directory default | Stable enough for readers as a default, but not a data-schema contract. |
0146 | `A000` | event instance and event index | `A` is the GPU `SEvt` instance, `B` is the CPU/G4 comparison instance. The numeric part is zero-padded event index width 3. |
0147 
0148 For run-level metadata, the same base and event relative directory are used
0149 without the final `A000` or `B000` event-index subdirectory. The app-level
0150 `Config` interface does not expose a separate save-mode field; prefer
0151 `Config::output_dir` for controlling where event folders are written.
0152 
0153 ### File schemas
0154 
0155 The table assumes the default full photon representation and unmerged hits:
0156 `Config::mode_lite` is `ModeLite::Unspecified` or `ModeLite::Standard`, and
0157 `Config::mode_merge` is `ModeMerge::Unspecified` or `ModeMerge::Separate`.
0158 Lite and merge modes replace the photon and hit component names with
0159 `photonlite`, `hitlite`, `hitmerged`, or `hitlitemerged` after
0160 `Config::Apply` synchronizes the app-level policy to `SEventConfig`.
0161 
0162 | File | Shape | Dtype | Meaning | Written by |
0163 |------|-------|-------|---------|------------|
0164 | `photon.npy` | `(N, 4, 4)` | `float32` | Final state of every photon as `sphoton`. | `EventMode::HitPhoton`, `EventMode::HitPhotonSeq`, `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0165 | `hit.npy` | `(H, 4, 4)` | `float32` | Subset of `photon.npy` whose `flagmask` satisfies the configured hitmask. | `EventMode::Hit`, `EventMode::HitPhoton`, `EventMode::HitPhotonSeq`, `EventMode::HitSeq`, `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0166 | `inphoton.npy` | `(N, 4, 4)` | `float32` | Input photons copied or narrowed from the configured input-photon array. Present only when input photons exist. | `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0167 | `record.npy` | `(N, R, 4, 4)` | `float32` | Full per-step `sphoton` records for each photon. `R` is `MaxRecord`; debug modes set it to `RecordLimit() == sseq::SLOTS == 32`. | `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0168 | `seq.npy` | `(N, 2, 2)` | `uint64` | Compact chronological step history: two `seqhis` words and two `seqbnd` words per photon. | `EventMode::HitPhotonSeq`, `EventMode::HitSeq`, `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0169 | `genstep.npy` | `(G, 6, 4)` | `float32` | Uploaded generation-step records. | `EventMode::Hit`, `EventMode::HitPhoton`, `EventMode::HitPhotonSeq`, `EventMode::HitSeq`, `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0170 | `domain.npy` | `(2, 4, 4)` | `float32` | Domain and config quads from `sevent::get_domain` and `sevent::get_config`. Mostly relevant to compressed records. | `EventMode::DebugLite`, `EventMode::DebugHeavy` |
0171 
0172 `N` is the number of photons in the event, `H` is the number of selected hits,
0173 `G` is the number of gensteps, and `R` is the configured record depth. A
0174 configured component with no backing data may be omitted.
0175 
0176 ### Event modes
0177 
0178 `Config::event_mode`, read from JSON `event.mode`, controls both GPU-to-CPU
0179 gathering and CPU-to-file saving after `Config::Apply` synchronizes it to the
0180 lower-level event system. The app-level default is `EventMode::Minimal`.
0181 
0182 | Mode | Files saved with default lite/merge settings | Notes |
0183 |------|----------------------------------------------|-------|
0184 | `EventMode::Minimal` | none | Gathers hits into memory only. Highest-statistics mode. |
0185 | `EventMode::Hit` | `hit.npy`, `genstep.npy` | Hit and genstep output only. |
0186 | `EventMode::HitPhoton` | `hit.npy`, `photon.npy`, `genstep.npy` | Does not save `record.npy` or `seq.npy`. |
0187 | `EventMode::HitPhotonSeq` | `hit.npy`, `photon.npy`, `seq.npy`, `genstep.npy` | Sets `MaxSeq=1`; sequence output without full records. |
0188 | `EventMode::HitSeq` | `hit.npy`, `seq.npy`, `genstep.npy` | Sequence output without full photon output. |
0189 | `EventMode::DebugLite` | `domain.npy`, `genstep.npy`, `inphoton.npy` when present, `photon.npy`, `hit.npy`, `record.npy`, `seq.npy` | Enables full records and sequence history. Low-statistics/debug mode. |
0190 | `EventMode::DebugHeavy` | `DebugLite` files plus lower-level arrays such as `aux.npy`, `prd.npy`, `tag.npy`, `flat.npy`, `sup.npy` | Deep debugging only; substantially higher memory use. |
0191 
0192 Use `EventMode::DebugLite` or `EventMode::DebugHeavy` when an external reader
0193 requires `record.npy`. Use `EventMode::HitPhotonSeq`, `EventMode::HitSeq`,
0194 `EventMode::DebugLite`, or `EventMode::DebugHeavy` when it requires
0195 `seq.npy`.
0196 
0197 ### Photon and record layout
0198 
0199 `photon.npy`, `hit.npy`, `inphoton.npy`, and each `record.npy` step store the
0200 same `sphoton` layout: a `(4, 4)` `float32` matrix. Numeric physics fields are
0201 stored as floats. Packed integer fields occupy `float32` slots and must be
0202 read through a `uint32` view.
0203 
0204 | Quad | `.x` | `.y` | `.z` | `.w` |
0205 |------|------|------|------|------|
0206 | `q0` | `pos.x` in mm | `pos.y` in mm | `pos.z` in mm | `time` in ns |
0207 | `q1` | `mom.x` | `mom.y` | `mom.z` | `hitcount_iindex` as `uint32` |
0208 | `q2` | `pol.x` | `pol.y` | `pol.z` | `wavelength` in nm |
0209 | `q3` | `orient_boundary_flag` as `uint32` | `identity` plus index extension as `uint32` | `index` low 32 bits as `uint32` | `flagmask` as `uint32` |
0210 
0211 `mom` and `pol` are direction/polarization vectors. `record.npy[i, s]` is the
0212 same layout for photon `i` at step slot `s`. Unused record slots are zero-filled;
0213 used slots are expected to be contiguous from slot 0 until the first all-zero
0214 step or until the configured record depth is reached.
0215 
0216 ### Packed fields
0217 
0218 Interpret the packed fields with `arr.view(np.uint32)`.
0219 
0220 `q1.w` packs hit count and intersected-geometry instance index:
0221 
0222 ```text
0223 bits 31-16: hitcount
0224 bits 15-0 : iindex
0225 ```
0226 
0227 `q3.x` packs the terminal flag, boundary, and orientation:
0228 
0229 ```text
0230 bit 31     : orient sign, where 1 means orient = -1 and 0 means orient = +1
0231 bits 30-16: boundary index, 15 bits
0232 bits 15-0 : terminal flag from OpticksPhoton.h
0233 ```
0234 
0235 Use these exact masks and shifts:
0236 
0237 ```python
0238 obf = q3[..., 0]
0239 flag = obf & 0x0000FFFF
0240 boundary = (obf & 0x7FFF0000) >> 16
0241 orient = np.where((obf & 0x80000000) != 0, -1.0, 1.0)
0242 ```
0243 
0244 `q3.y` stores a 24-bit identity in its low bits and extends the photon index in
0245 its high 8 bits. `q3.z` stores the low 32 bits of the photon index:
0246 
0247 ```python
0248 identity = q3[..., 1] & 0x00FFFFFF
0249 index = ((q3[..., 1].astype(np.uint64) >> 24) << 32) | q3[..., 2].astype(np.uint64)
0250 flagmask = q3[..., 3]
0251 ```
0252 
0253 `identity` is currently `sensor_identifier + 1`; zero means the photon did not
0254 end on a sensor. `flagmask` is the cumulative OR of every flag set during the
0255 photon lifetime. Each `sphoton::set_flag(f)` updates both the terminal `flag`
0256 and `flagmask |= f`.
0257 
0258 ### Flag values
0259 
0260 The low 16 `OpticksPhoton.h` flags are the values stored by the terminal
0261 `flag` field and cumulative `flagmask`.
0262 
0263 | Flag | Hex | Abbrev | Meaning |
0264 |------|-----|--------|---------|
0265 | `CERENKOV` | `0x0001` | `CK` | Cerenkov generation |
0266 | `SCINTILLATION` | `0x0002` | `SI` | Scintillation generation |
0267 | `TORCH` | `0x0004` | `TO` | Torch source |
0268 | `BULK_ABSORB` | `0x0008` | `AB` | Absorbed in bulk material |
0269 | `BULK_REEMIT` | `0x0010` | `RE` | Re-emitted, including WLS |
0270 | `BULK_SCATTER` | `0x0020` | `SC` | Rayleigh scattered |
0271 | `SURFACE_DETECT` | `0x0040` | `SD` | Detected at a surface |
0272 | `SURFACE_ABSORB` | `0x0080` | `SA` | Absorbed at a surface |
0273 | `SURFACE_DREFLECT` | `0x0100` | `DR` | Diffuse reflection at a surface |
0274 | `SURFACE_SREFLECT` | `0x0200` | `SR` | Specular reflection at a surface |
0275 | `BOUNDARY_REFLECT` | `0x0400` | `BR` | Fresnel reflection at a boundary |
0276 | `BOUNDARY_TRANSMIT` | `0x0800` | `BT` | Transmitted through a boundary |
0277 | `NAN_ABORT` | `0x1000` | `NA` | Aborted due to NaN, usually geometry-related |
0278 | `EFFICIENCY_COLLECT` | `0x2000` | `EC` | Collected by PMT efficiency handling |
0279 | `EFFICIENCY_CULL` | `0x4000` | `EL` | Culled by PMT efficiency handling |
0280 | `MISS` | `0x8000` | `MI` | Missed all geometry |
0281 
0282 ### Sequence layout
0283 
0284 `seq.npy` stores `sseq` as `(N, 2, 2)` `uint64`:
0285 
0286 ```python
0287 seqhis = seq[:, 0, :]  # two uint64 words per photon
0288 seqbnd = seq[:, 1, :]  # two uint64 words per photon
0289 ```
0290 
0291 `sseq::NSEQ == 2`, `sseq::BITS == 4`, `sseq::SLOTMAX == 16`, and
0292 `sseq::SLOTS == 32`. Each photon therefore has 32 chronological history slots.
0293 Slot `0..15` is stored in word 0; slot `16..31` is stored in word 1. Within a
0294 word, each slot is one 4-bit nibble:
0295 
0296 ```python
0297 iseq = slot // 16
0298 shift = 4 * (slot - iseq * 16)
0299 nibble = (seqhis[i, iseq] >> shift) & 0xF
0300 flag = 0 if nibble == 0 else 1 << (nibble - 1)
0301 ```
0302 
0303 The stored nibble is `FFS(flag) & 0xF`, where `FFS` is find-first-set using
0304 one-based bit positions. For example, `TORCH` (`0x0004`) stores nibble `3`,
0305 and `SURFACE_DETECT` (`0x0040`) stores nibble `7`. A zero nibble marks no
0306 recorded flag and is commonly used as the end of the sequence. Because the
0307 nibble has only four bits and zero is reserved, `MISS` (`0x8000`, FFS 16) is
0308 not representable in `seq.npy` history slots.
0309 
0310 `seqbnd` stores `boundary & 0xF` for the same slots. Use `record.npy` or
0311 `photon.npy` when the full 15-bit boundary index is required.
0312 
0313 ### Hit semantics
0314 
0315 Hits are selected from the final photon array by the configured hitmask:
0316 
0317 ```text
0318 (flagmask & hitmask) == hitmask
0319 ```
0320 
0321 The current default hitmask is `SD`, meaning `SURFACE_DETECT` (`0x0040`).
0322 Some PMT efficiency workflows use `EC`, meaning `EFFICIENCY_COLLECT`
0323 (`0x2000`). The app-level `Config` interface does not currently expose the
0324 hitmask; the event metadata records the numeric `hitmask`, and readers should
0325 prefer that metadata over hard-coded assumptions.
0326 
0327 `hit.npy` is a copy of the selected rows from `photon.npy`, not a different
0328 layout. The original photon index remains available through the packed `q3.y`
0329 and `q3.z` index fields.
0330 
0331 Photons that exhaust the bounce limit are not given a distinct terminal flag
0332 by the output format. With default `MaxBounce=31`, debug records have up to 32
0333 slots. A reader can identify likely bounce-limit truncation by checking photons
0334 whose non-zero record-step count equals `record.shape[1]`.
0335 
0336 ### Python reader pattern
0337 
0338 Use shape and dtype checks before decoding packed fields:
0339 
0340 ```python
0341 from pathlib import Path
0342 import numpy as np
0343 
0344 event_dir = Path("path/from/Config.output_dir/ALL0_no_opticks_event_name/A000")
0345 record = np.load(event_dir / "record.npy")
0346 
0347 assert record.dtype == np.float32
0348 assert record.ndim == 4 and record.shape[2:] == (4, 4)
0349 
0350 u32 = record.view(np.uint32)
0351 q3 = u32[..., 3, :]
0352 
0353 terminal_flag = q3[..., 0] & 0x0000FFFF
0354 boundary = (q3[..., 0] & 0x7FFF0000) >> 16
0355 orient = np.where((q3[..., 0] & 0x80000000) != 0, -1.0, 1.0)
0356 identity = q3[..., 1] & 0x00FFFFFF
0357 photon_index = ((q3[..., 1].astype(np.uint64) >> 24) << 32) | q3[..., 2].astype(np.uint64)
0358 flagmask = q3[..., 3]
0359 
0360 step_used = np.any(record.reshape(record.shape[0], record.shape[1], 16) != 0, axis=2)
0361 step_count = step_used.sum(axis=1)
0362 ```
0363 
0364 For `photon.npy`, `hit.npy`, and `inphoton.npy`, use the same decoding with
0365 shape `(N, 4, 4)` and `q3 = arr.view(np.uint32)[:, 3, :]`.