Skip to content

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 shell
  • powerlaw_shellmodel - Power-law shell profiles
  • unigrid - Uniform Cartesian grid
  • infiniteslab - Infinite slab
  • pointcloud_voronoi - Voronoi tessellation (experimental)
  • pointcloud_voronoi_tree - Tree-accelerated Voronoi (incomplete)
  • pointcloud_sph - SPH interpolation (incomplete)

Driver Types

  • mcrtsimulation - Monte Carlo Radiative Transfer
  • raytracer - 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: 1 saves almost nothing and gives up no size — level is not a useful escape hatch here.
  • compress_float32 is the efficient lever: 1.71× for an 11% wall cost. The columns that dominate volume are noisy float64 physics 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.