Configuration Reference
Thor reads a single YAML configuration file. This page documents the available options.
File Structure
# Global settings
## Required
dataset_type: 'shellmodel'
driver_type: 'mcrtsimulation'
## Optional
device: "cpu" # options: "cpu", "cpu-openmp", "gpu", "multigpu"
log_level: info # options: trace, debug, info, warn, error
log_dir: "./logs"
# Dataset section (name must match dataset_type)
shellmodel:
# ...
Global Settings
| Parameter | Type | Default | Description |
|---|---|---|---|
dataset_type |
string | required | Dataset geometry type |
driver_type |
string | required | Simulation driver |
device |
string | SYCL default | Compute device (see Device Options) |
selftest |
bool | true |
Run device self-tests at startup |
log_level |
string | "debug" |
Logging verbosity |
log_dir |
string | "./logs" |
Log output directory |
Device Options
| Value | Description |
|---|---|
"cpu" |
Default SYCL CPU selector |
"cpu-openmp" |
Explicitly select the OpenMP CPU backend |
"gpu" |
SYCL GPU selector |
"multigpu" |
Multi-GPU with one MPI rank per GPU (requires matching rank count and GPU count per node). Not yet broadly supported. |
Tip
"cpu" lets the SYCL runtime pick a CPU backend, which may not be OpenMP. Use "cpu-openmp" to select the OpenMP backend explicitly.
Dataset Types
shellmodel- Spherical shellpowerlaw_shellmodel- Power-law shell profilesunigrid- Uniform Cartesian gridinfiniteslab- Infinite slabpointcloud_voronoi- Voronoi tessellation (experimental)pointcloud_voronoi_tree- Tree-accelerated Voronoi (incomplete)pointcloud_sph- SPH interpolation (incomplete)
Driver Types
mcrtsimulation- Monte Carlo Radiative Transferraytracer- Forward ray tracing (projections, absorption spectra, tracers, etc)
MCRT Simulation Settings
mcrtsimulation:
outputpath: output
overwrite: true
linename: Lya
# Photon control
nphotons_max: 100000
nphotons_step_max: 100000
nsteps_per_photon_max: 10000000
# Physics
xcrit: 3.0
acc_scheme: Smith15
interactor: ResonantWithDustInteractor
# Stepping
max_step: 1.0
min_step: 0.0
# Boundary conditions
pbc: false
hubble_flow: 300.0 # km/s/Mpc (for cosmological simulations)
# Peeling
use_peeling: true
peeling:
dist_max: 0.6
tau_max: 100.0
# Emission model
emissionmodel:
# ...
# Output processors
outputprocessors:
# ...
MCRT Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
outputpath |
string | required | Output directory |
overwrite |
bool | false |
Overwrite existing output |
linename |
string | required | Resonant line to simulate (see Line Names) |
nphotons_max |
int | 1000000 |
Max photons in memory |
nphotons_step_max |
int | 1000000 |
Max photons spawned per step |
nsteps_per_photon_max |
int | 1000000000 |
Max steps per photon |
xcrit |
float | 0.0 |
Forced critical frequency for line wings |
acc_scheme |
string | "none" |
Acceleration scheme |
interactor |
string | - | Interaction model |
min_step |
float | 0.0 |
Minimum step length (not honored by all dataset types) |
max_step |
float | 1.0 |
Maximum step length |
max_dlambda |
float | - | Maximum wavelength bin shift per step |
pbc |
bool | false |
Periodic boundary conditions |
hubble_flow |
float | - | Hubble flow velocity (km/s/Mpc) |
use_peeling |
bool | false |
Enable peeling photons |
save_originalphotons |
bool | true |
If false, skip creating original/data.h5 (the per-step snapshot of escaped photons). Useful when only the peel image is needed and disk footprint is a concern. Incompatible with restart: true. |
Line Names
The linename selects the resonant line to simulate. It determines the scattering cross-section, the ion density field that drives opacity, and which Cloudy emission/absorption fields to load. Use one of the following abbreviations:
| Abbreviation | Line | Notes |
|---|---|---|
Lya |
Lyman-alpha (H I 1215.67 A) | |
MgII |
Mg II doublet (2796 + 2803 A) | Requires Cloudy tables. Use with ResonantDoubletInteractor for doublet treatment |
FeXXV |
Fe XXV (1.85 A) | Requires Cloudy tables |
OVII |
O VII (21.60 A) | Requires Cloudy tables |
Note
Only Lya and FeXXV are currently supported with pointcloud_voronoi datasets. MgII and OVII density field mappings are not yet implemented for point cloud datasets, but work with analytical datasets (e.g. shellmodel, unigrid).
Acceleration Schemes
Only use values other than none with Lyman-alpha.
| Value | Description |
|---|---|
none |
No acceleration |
Smith15 |
Smith et al. 2015 |
Laursen09 |
Laursen et al. 2009 |
Interactors
| Value | Description |
|---|---|
SimpleInteractor |
Basic resonant scattering |
ResonantWithDustInteractor |
Includes dust absorption |
ResonantDoubletInteractor |
Doublet lines (e.g., MgII) |
Dust
When interactor: ResonantWithDustInteractor, the dust absorption coefficient
field DustAbsorptionCoefficient [cm⁻¹] is built from a top-level dust: block:
dust:
model: constant # constant | laursen | colt
variant: LAURSEN_SMC # SMC | LAURSEN_SMC (laursen/colt only)
k_dust_value: 1.0e-21 # [cm^-1] (constant only)
z_ref: 0.005 # reference metallicity (laursen/colt)
f_ion: 0.01 # HII-region dust survival (laursen/colt)
t_sputter: 1.0e6 # [K], thermal sputtering (colt only)
dust_to_gas_field: D # optional input field name (colt only)
| Model | Formula | Inputs |
|---|---|---|
constant |
k = k_dust_value |
— |
laursen |
k = σ · (nHI + f_ion·nHII) · Z/z_ref — Laursen+09 |
HIdensity, HIIdensity, GFM_Metallicity |
colt |
k = f_surv · nH · (σ/z_ref) · (Z+D) with f_surv = (T>t_sputter ? 0 : 1+(f_ion-1)·xHII) |
Hdensity, HIdensity, HIIdensity, Temperature, GFM_Metallicity, optional D field |
Byrohl & Nelson 2025 validates only the constant model (infinite slab,
Fig. 4). The laursen and colt models implement literature formulae
(rascas, COLT) but are not yet independently validated in THOR.
Dataset support: laursen and colt require recipe-system field access
and are currently PointCloud-only. Shell, uniform-grid, and infinite-slab
datasets support only constant. For infinite slab, the legacy
infiniteslab.tau_dust key still works and takes precedence over
dust.k_dust_value.
Debug Settings
THOR has two stuck-photon detectors, sharing one disposition vocabulary
(off / warn / finish / error):
| Detector | Fires when | Config key | Default |
|---|---|---|---|
| No-progress (real geometric stall) | consecutive steps without scattering ≥ max_steps_without_scatter |
mcrtsimulation.stuck_photon_mode |
error |
| Step-budget (debug heuristic) | exceeds nsteps_per_photon_max steps in one kernel launch |
mcrtsimulation.debug.step_budget_mode |
off |
mcrtsimulation:
stuck_photon_mode: error # real stall guard; finish for unattended sweeps
max_steps_without_scatter: 1000000
debug:
step_budget_mode: off # debug only; also fires on thick scatterers
The no-progress counter resets on every scatter, so thick scatterers never trip it — hence its
error default. The step-budget signal fires on any budget-exhausting photon, including
legitimately thick ones, so keep it off/warn: finish biases results and error aborts valid
runs. (No-progress detection compiles out with THOR_NSTEPS_SINCE_SCATTER_ATTR, default ON. The old
debug.stuck_photon_print/stuck_photon_finish keys are deprecated, mapped to step_budget_mode.)
Peeling Settings
When use_peeling: true, peeling photons are generated at each scattering event and propagated towards the observer:
mcrtsimulation:
use_peeling: true
peeling:
dist_max: 0.6 # maximum observer distance (box widths)
tau_max: 100.0 # maximum optical depth before discarding peel
| Parameter | Type | Default | Description |
|---|---|---|---|
dist_max |
float | - | Maximum distance for peeling rays (in box widths) |
tau_max |
float | 30.0 |
Optical depth threshold; peeling photons above this are discarded |
Emission Models
Single Source
emissionmodel:
mode: "singlesource"
singlesource:
nphotons: 1000
lum_total: 1e42
position: [0.5, 0.5, 0.5]
forced_weight: 1.0 # optional
List Source
emissionmodel:
mode: "listsource"
listsource:
loader: gadget
nphotons_per_source_min: 1
nphotons_per_source_max: 1000
spatial_filter: # optional, also supported by datasetsource
mode: sphere # none | cube | sphere
center: [0.5, 0.5, 0.5]
radius: 0.25
Multi Source Options
The listsource and datasetsource generators share these options:
| Parameter | Type | Default | Description |
|---|---|---|---|
spatial_filter |
map | mode: none |
Optional pre-filter on emitter positions before luminosity thresholding. Supports sphere with center and radius, or cube with box: [[x0,x1],[y0,y1],[z0,z1]], in current box coordinates. |
lum_threshold |
float | 0.0 |
Absolute luminosity floor: emitters with lum_total < lum_threshold are dropped. Set negative to disable. |
lum_threshold_fraction |
float | -1.0 |
Fractional luminosity floor: keep brightest emitters whose cumulative luminosity captures this fraction of the total. Set negative to disable. |
lum_threshold_policy |
string | stricter |
When both lum_threshold and lum_threshold_fraction are set: stricter keeps only emitters passing both cuts; looser keeps emitters passing either. |
Dataset Source
emissionmodel:
mode: "datasetsource"
datasetsource:
linename: HalphaRecomb # required: which line/recipe drives the Emissivity field
emission_position: centroid # optional: 'centroid' (default) or 'uniform_in_cell'
spatial_filter: # optional, also supported by listsource
mode: sphere # none | cube | sphere
center: [0.5, 0.5, 0.5]
radius: 0.25
lum_threshold: 1.0e-6 # optional absolute luminosity cut
lum_threshold_fraction: 0.5 # optional brightest-emitter cumulative luminosity fraction
lum_threshold_policy: "stricter" # "stricter" = both cuts, "looser" = either cut
| Parameter | Type | Default | Description |
|---|---|---|---|
linename |
string | required | Emission line / recipe key used to populate the dataset's Emissivity field |
emission_position |
string | centroid |
Where inside a cell photons are spawned. centroid reuses the cell position (all photons from one cell share it). uniform_in_cell draws a fresh per-photon position uniformly inside the cell using a 200-step hit-and-run MCMC. Only supported for PointCloudDataset with the Voronoi interpolator. |
Use uniform_in_cell when the cell linear size is comparable to or larger than the radiative transfer length scale you care about resolving — otherwise the per-cell centroid is fine and avoids the per-photon sampling cost.
Output Processors
Output processors transform or aggregate photon data. The photon processor (raw photon dump) is the most common.
Common parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
type |
string | required | Processor type ("photon", "histogram1d", "apertureSpectrum", "sbmap", etc.) |
active |
bool | true |
Enable/disable this processor |
streams |
list of strings | all streams | Restrict processor to specific photon streams |
Stream Filtering
THOR runs up to three photon streams: original (escaped photons), peel (peeling-off photons), and input (initial photon state). By default, each output processor is active for all streams. The streams parameter restricts a processor to specific streams:
outputprocessors:
- type: "photon"
active: true
- type: "sbmap"
streams: ["peel"] # only process peeling photons (default for sbmap)
nbins_x: 64
nbins_y: 64
left_x: 0.0
right_x: 1.0
left_y: 0.0
right_y: 1.0
weight: "weight"
- type: "histogram1d"
# no streams → active for all streams
field: "dlambda"
weight: "weight"
left: -1.5
right: 1.5
nbins: 30
assess_convergence: true
convergence_threshold: 0.1
Some processors have built-in defaults. The frame-based processors — sbmap, apertureSpectrum, and the histogram1d aperture — need a fixed observer frame: without an explicit frame they default to streams: ["peel"] (the line of sight is inferred from the peeling observer directions), but with an explicit line_of_sight or lines_of_sight they default to streams: ["input"], since a fixed frame is meant for the intrinsic source photons. An explicit streams in the config always overrides these defaults.
Automatic raw output
If no raw, photon, or trace processor is listed, THOR automatically injects one that runs on all streams. This ensures raw photon data is always written. To suppress it, add an explicit processor of one of these types with active: false.
HDF5 Compression
Output datasets are chunked and compressed with HDF5's built-in shuffle + gzip filters. Both are part of libhdf5 itself — no filter plugin is needed, and h5py, h5ls, and any other stock HDF5 reader decompress transparently.
The default depends on the processor class, because the two write patterns have very different costs:
| processor class | compress default |
why |
|---|---|---|
sbmap, apertureSpectrum, histogram1d, histogram2d |
true |
one compression pass at finalize, nothing waits on it, lossless — a 512² SB map shrinks ~32× for no measurable cost |
raw, photon, trace (and the driver's trace dump) |
false |
appended per batch inside the writer mutex on the transport hot path — roughly 2× wall time on a write-bound run |
An explicit compress key beats the class default, and a per-processor value beats a driver-level one.
Without compress_float32, compression is a storage change only: values round-trip bit-for-bit. A raw writer left at its default keeps its historical 1024-row chunks, so the streamed columns' layout and dtype are exactly as before. (That is a per-dataset guarantee. Whole HDF5 files are not reproducible byte for byte between runs in any case — MCRT photon ordering varies with thread scheduling.)
mcrtsimulation:
compress_float32: true # inherited by every processor below
outputprocessors:
- type: "photon"
compress: true # opt the streamed columns in, against their class default
compress_level: 0 # narrow but do not deflate — the efficient point (see below)
- type: "sbmap"
compress_float32: false # override one key; still compressed by class default
nbins_x: 512
nbins_y: 512
left_x: 0.0
right_x: 1.0
left_y: 0.0
right_y: 1.0
| Parameter | Type | Default | Description |
|---|---|---|---|
compress |
bool | class-specific (see above) | Chunked layout + filters |
compress_level |
int | 4 |
gzip/deflate level, 0–9. 0 keeps chunking and shuffle but skips deflate. Level 4 captures nearly all of level 9's ratio at a fraction of the CPU. |
compress_shuffle |
bool | true |
Byte-shuffle filter. Groups the like-valued exponent bytes of a float array together so gzip can exploit them. Large win, essentially free. |
compress_float32 |
bool | false |
Store float64 arrays as float32. Lossy — about 7 significant decimal digits survive. Accumulation always stays in full precision; only the written array is narrowed. Requires compress. |
compress_min_bytes |
int | 65536 |
Write-once datasets only. Maps, spectra and histograms below this size stay contiguous and unfiltered — chunk and filter overhead is not worth it. Set to 0 to always chunk. Streamed columns are exempt: they are resizable, so their final length is unknown when the dataset is created and the decision cannot depend on it. |
compress_chunk_bytes |
int | 1048576 |
Target uncompressed bytes per chunk for write-once datasets. |
compress_append_chunk_bytes |
int | 262144 |
Target bytes per chunk for streamed columns. Should stay comfortably under both HDF5's default 1 MiB chunk cache and one batch's worth of rows (nphotons_step_max), or partially-filled chunks get decompressed and recompressed on every batch that touches them. Measured at a 50k-row batch: 64 KiB → 38.0 MB / 0.99 s writer, 256 KiB → 39.2 MB / 1.13 s, 1 MiB → 49.3 MB / 1.68 s. |
compress_float32 and geometry
The downcast applies to bulk payloads only (images, spectra, photon columns). The line_of_sight, e_x, and e_y frame datasets always keep full precision, because readers use them to reconstruct image-plane coordinates — a narrowed basis vector would propagate error into positions rather than only into a plotted value. Dataset attributes (bin edges, weights, radii) are never downcast either.
Chunk shape. Chunks are sized by target bytes, trimming the slowest-varying axis first, so a [nlos, nx, ny] map chunks into whole images or contiguous row bands of one image. Sizing matters for the ratio, not just for the cache: deflate compresses each chunk independently, so a chunk far below its window wastes compressibility. On a real 512×512 sbmap, one-row 4 KiB chunks reach 27.0× where 1 MiB chunks reach 31.9× — and 48.2× vs 65.0× once compress_float32 halves the element size.
Measured on a 5000-photon shell-model peel run (512² sbmap, ~194k peel photons):
| Setting | Total output | vs. all-off | sbmap/image |
|---|---|---|---|
| everything off | 33.9 MB | 1.0× | 2.10 MB |
| maps + streamed columns | 14.1 MB | 2.4× | 65.8 KB (31.9×) |
+ compress_float32 |
6.7 MB | 5.1× | 31.8 KB (65.9×) |
Ratios are strongly data-dependent. Sparse, smooth arrays such as SB maps compress enormously; a float64 column of noisy per-photon physics values (wavelength offsets, optical depths) has effectively random mantissa bits and compresses only ~1.2×. That asymmetry is why compress_float32 buys much more on the photon columns than on the maps.
Opting the streamed columns in
The class default keeps them uncompressed because gzip there runs inside the writer mutex on the transport hot path. Measured on a deliberately write-bound run — optically thin shell (τ ≈ 1), 2M photons, peeling off, 4M rows / 422 MB of photon columns. Medians of 3 runs; the writer column is time spent inside the HDF5 mutex, which is 22% of wall at baseline:
Setting on the photon processor |
wall | writer | size |
|---|---|---|---|
| class default (off) | 2.97 s | 0.65 s | 422 MB |
compress: true (gzip-4) |
6.19 s (2.08×) | 4.22 s | 180 MB (2.35×) |
compress: true, compress_level: 1 |
5.93 s (2.00×) | 3.94 s | 180 MB (2.34×) |
compress: true, compress_level: 0, compress_float32 |
3.29 s (1.11×) | 1.10 s | 248 MB (1.71×) |
compress: true, compress_float32 |
4.67 s (1.57×) | 2.80 s | 88 MB (4.82×) |
Reading this:
- gzip on photon columns is a poor trade in isolation. It roughly doubles the wall time of a write-bound run for 2.35×. Dropping to
compress_level: 1saves almost nothing and gives up no size — level is not a useful escape hatch here. compress_float32is the efficient lever: 1.71× for an 11% wall cost. The columns that dominate volume are noisyfloat64physics values whose mantissa bits are near-random — deflate does full work on them for ~1.2×, whereas narrowing gives a guaranteed 2× for the price of a type conversion.- Combine them when size matters most: 4.82× for a 1.57× wall cost.
Both runs above had IO effectively free (tmpfs, and a local disk whose writes fit in page cache), so they isolate CPU cost. On a bandwidth-limited filesystem — cluster scratch, NFS, a shared parallel FS — writing 4.8× fewer bytes shifts the balance back toward compression by an amount these numbers do not capture. Measure your own case with log_level: trace and compare the Writing N entries to disk took ... MB/s lines.
Photon Output
Writes escaped photon data to HDF5.
| Parameter | Type | Description |
|---|---|---|
active |
bool | Enable this processor |
Histogram 1D
Experimental
This feature is experimental and may change.
Bins photon field values into a histogram (e.g., spectrum).
| Parameter | Type | Default | Description |
|---|---|---|---|
quantity |
string | "dlambda" |
Field to histogram (e.g., "dlambda" for a spectrum) |
weight |
string | "weight" |
Weight field (currently only "weight") |
left |
float | required | Left bin edge |
right |
float | required | Right bin edge |
nbins |
int | 100 |
Number of bins |
name |
string | (=quantity) |
Output dataset name under /histogram1d/; set it to run several histograms of the same quantity (e.g. a full-field and an aperture spectrum) in one file without colliding |
aperture_radius |
number or string | (off) | If set, restrict to photons within this radius of the aperture center — enables aperture mode. Unit-aware: a bare number is a box fraction; a string takes a unit, e.g. "7 pkpc" or "1 arcsec" (see below) |
aperture_center |
vec2 | [0.5, 0.5] |
Aperture center in image-plane coordinates (default the projection center) |
center |
vec3 | [0.5, 0.5, 0.5] |
Projection center for the image-plane mapping (shared with sbmap) |
north |
vec3 | [0, 0, 1] |
Image orientation hint (shared with sbmap) |
assess_convergence |
bool | false |
Check convergence |
convergence_threshold |
float | 0.01 |
Convergence tolerance |
The optional aperture restricts the spectrum to a circular region of the image plane, using the same centered projection convention as sbmap (box center → image (0.5, 0.5)). With multiple peeling observers it applies per-observer (each photon is masked against its own line-of-sight frame) and the output is stacked [nlos, nbins], matching the full-field spectrum. This produces the aperture-integrated line profile during the run, without dumping raw photons for offline masking.
The single aperture_radius key is unit-aware:
| Form | Meaning |
|---|---|
aperture_radius: 0.02 |
Box fraction — same coordinates as the sbmap left/right bounds (0.02 = 2%-of-box radius) |
aperture_radius: "7 pkpc" |
Physical kpc — converted via the dataset's length_unit (already physical cm, so no redshift factor) |
aperture_radius: "1 arcsec" |
Angular — converted to physical kpc via the flat-ΛCDM angular-diameter distance at the snapshot redshift, using the dataset's cosmology (Ω_m, Ω_Λ, h from the simulation header). Requires a cosmological dataset (z > 0) |
kpc is accepted as an alias for pkpc. An unknown unit, or arcsec on a non-cosmological dataset, is a config error.
Aperture Spectrum (apertureSpectrum)
Experimental
This feature is experimental and may change.
Produces a spectrum for several concentric circular apertures at once, in a single binning pass. It is the multi-aperture generalization of the histogram1d aperture: instead of one aperture_radius, you give a list of aperture_radii and get one enclosed spectrum per radius (all flux within that radius), written to /aperturespectrum/.
| Parameter | Type | Default | Description |
|---|---|---|---|
aperture_radii |
list | required | Aperture radii. Each entry is unit-aware exactly like the histogram1d aperture_radius (bare number = box fraction, or "7 pkpc" / "1 arcsec"). Sorted ascending and de-duplicated at init |
nbins |
int | 100 |
Number of spectral bins |
left / right |
number | required | Spectral binning range (in quantity units) |
quantity |
string | dlambda |
Photon field to bin. On the input stream, dlambda projects the emitting-cell velocity separately for each LOS |
weight |
string | weight |
Photon weight field (only weight supported) |
aperture_center |
vec2 | [0.5, 0.5] |
Aperture center in image-plane coordinates |
center |
vec3 | [0.5, 0.5, 0.5] |
Projection center for the image-plane mapping (shared with sbmap/histogram1d) |
north |
vec3 | [0, 0, 1] |
Image orientation hint (shared with sbmap) |
line_of_sight |
vec3 | (from peeling) | Explicit observer direction for non-peel streams |
name |
string | (=quantity) |
Output dataset name under /aperturespectrum/ |
Each photon is projected onto its observer's image plane and assigned to the radial bucket between the sorted radii (a single upper_bound); a photon beyond the largest radius is dropped. At finalize the buckets are summed inner→outer, so aperture k holds the flux enclosed within radius r_k — the same "inside radius" meaning as the single histogram1d aperture, for every radius simultaneously. Doing the bucket sort once avoids re-masking the photon stream per aperture.
Like the histogram1d aperture, it needs a fixed observer frame. Without an explicit frame it defaults to the peel stream (peeling.lines_of_sight); with an explicit line_of_sight or a lines_of_sight list it defaults to the input stream (intrinsic source photons). An explicit streams overrides either default. Output is [n_apertures, nbins] for a single observer, or stacked [nlos, n_apertures, nbins] with N>1 observers or any explicit lines_of_sight broadcast list (even a one-entry list → [1, n_apertures, nbins]); per-observer frames are in /aperturespectrum/line_of_sight, /e_x, /e_y. The sorted radii and projection geometry are stored as dataset attributes (aperture_radii, n_apertures, aperture_center, center, north).
For an input-stream aperture spectrum, quantity: dlambda is evaluated in the requested observer direction rather than blindly reusing the stored source-photon direction. THOR caches the emitting cell's bulk velocity once per photon batch and converts the frequency separately for every LOS. With write_inputphotons_restframe: true (the default), it evaluates dfreq_los = dfreq_gas + nu0 (v_cell . los) / c. With write_inputphotons_restframe: false, stored dfreq already contains the Doppler shift along the photon's original direction k, so it evaluates dfreq_los = dfreq + nu0 [v_cell . (los - k)] / c; this replaces the old directional shift instead of applying the velocity twice. This local peculiar-velocity transform does not add the peel photon's final-leg Hubble shift represented by dlambda_peel_shifted.
outputprocessors:
- type: "apertureSpectrum"
quantity: "dlambda"
lines_of_sight: [[1, 0, 0], [0, 1, 0], [-1, 0, 0]]
aperture_radii: ["1 pkpc", "5 pkpc"]
nbins: 200
left: -10.0
right: 10.0
outputprocessors:
- type: "apertureSpectrum"
quantity: "dlambda_peel_shifted"
aperture_radii: ["1 arcsec", "2 arcsec", "5 arcsec"]
nbins: 200
left: -10.0
right: 10.0
Surface Brightness Map (sbmap)
Experimental
This feature is experimental and may change.
Projects photon positions onto a 2D image plane perpendicular to the line of sight. Produces an HDF5 dataset at /sbmap/image.
| Parameter | Type | Default | Description |
|---|---|---|---|
nbins_x |
int | required | Number of pixels in x |
nbins_y |
int | required | Number of pixels in y |
left_x |
float | required | Left edge of image in x |
right_x |
float | required | Right edge of image in x |
left_y |
float | required | Left edge of image in y |
right_y |
float | required | Right edge of image in y |
weight |
string | "weight" |
Weight field (currently only "weight" supported) |
north |
vec3 | [0, 0, 1] |
North hint vector for image orientation |
center |
vec3 | [0.5, 0.5, 0.5] |
Projection center; maps to image coordinate (0.5, 0.5) for every observer |
line_of_sight |
vec3 | (from photons) | Explicit LOS direction (required for non-peel streams) |
los_indices |
list of ints | (all observers) | Select peeling observers by original zero-based index, preserving the listed order |
los_stride |
int | (all observers) | Select original observer indices 0, N, 2N, ...; must be positive |
streams |
list | ["peel"], or ["input"] with an explicit frame |
Allowed streams; peel-only by default, input-only when line_of_sight/lines_of_sight is set (an explicit streams overrides) |
The image frame is computed from the line of sight direction and the north hint vector. By default (peel stream), the LOS comes from the peeling observer directions. Providing an explicit line_of_sight (or a lines_of_sight list) images the intrinsic source photons instead: it routes the map to the input stream automatically — no streams override needed — and a lines_of_sight list broadcasts each source photon into every frame, giving a stacked [nlos, nbins_x, nbins_y] output. An explicit frame combined with a forced streams: ["peel"] is rejected, since the peel observer directions would otherwise silently override the configured frame.
Image coordinates use a centered convention: image_x = (pos - center)·e_x + 0.5 (and likewise for y), so the projection center — the box center by default — lands at image coordinate (0.5, 0.5) for every observer direction. For an axis-aligned observer the unit box projects exactly onto [0, 1] in each image axis, so the default [0, 1] bounds capture the full cross-section. For an off-axis observer the projected box footprint is larger (e.g. a [1, 1, 1] line of sight spans about [-0.207, 1.207]), so photons near the projected corners fall outside [0, 1] and are clipped — widen left/right (or accept the central crop) for diagonal lines of sight. Zooming means narrowing left/right symmetrically around 0.5, or moving center onto the target. For the canonical +z observer this is identical to projecting raw box coordinates. If every photon an observer received falls outside the binning range, a warning is emitted at finalization (the image would otherwise be silently empty).
With multiple peeling observer directions (peeling.lines_of_sight, N>1), one image slab per observer is accumulated and written as a stacked [nlos, nbins_x, nbins_y] dataset, with per-observer frames stored as [nlos, 3] datasets (/sbmap/line_of_sight, /sbmap/e_x, /sbmap/e_y).
los_indices and los_stride are mutually exclusive, per-processor selectors. They reduce only the SB-map slabs retained in memory and written to disk; peeling photons are still generated and transported for every configured observer, so other processors such as apertureSpectrum continue to receive all observers. Explicit indices must be unique, non-negative, in range, and non-empty. Selection requires peeling observer directions and therefore cannot be used with an inferred non-peel line of sight.
For a subset selected from a multi-observer context, /sbmap/image keeps the shape [K, nbins_x, nbins_y] and the frame datasets keep [K, 3], including when K is one. The image dataset receives a los_indices attribute whose entries give the original observer index for each output slab.
Dataset-Specific Configuration
See Datasets for detailed configuration of each dataset type.