Skip to content

MCRT simulation and sources

Select driver_type: mcrtsimulation for Monte Carlo radiative transfer and place its settings under mcrtsimulation:. See General configuration for global and shared settings, and Output processors for photon streams, spectra, images, and compression.

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
  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 Nonempty output directory path
overwrite bool false Permit cleanup and replacement of existing output; otherwise the directory must be absent or empty
restart bool false Simulation restart is unsupported; true fails preflight
linename / line.name string required Resonant line to simulate (see Line Names)
line.density_field string legacy species default Ion number-density recipe; see Ion density
density_check_lenient bool false Allow a synthetic Density fallback when no field is explicitly selected
line.phase_function string auto Atomic default, or isotropic, rayleigh, half-rayleigh, lya
line.deuterium_fraction number 0 Neutral isotope number ratio nDI/nHI; hydrogen Lyα only
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 in each kernel launch
xcrit float 0.0 Minimum core-skipping frequency for Laursen09, in Doppler units
acc_scheme string "none" Acceleration scheme
interactor string SimpleInteractor Interaction model; unknown names are rejected
sort_photons bool false Sort photons for spatial locality
use_safety_radius bool true Reuse cached safety radii where supported by the dataset
use_atau0nl bool false Compute nonlocal opacity; an explicit populate mapping takes precedence
min_step float 0.0 Finite nonnegative minimum step in box units (not honored by all dataset types)
max_step float 1.0 Finite positive maximum step in box units
max_step_auto bool false Apply dataset step limits while retaining max_step as an upper bound
local_step_limits bool false Use position-dependent dataset limits; omission warns when the dataset supports them
dist_max float -1.0 Distance cap from the last scattering position, in box units; negative disables
allow_outofbox bool false Allow propagation outside the box
max_dlambda number 0 Signed wavelength offset (Å) used to derive a Hubble flight-distance limit from line centre; zero disables
pbc bool false Periodic boundary conditions
hubble_flow float 0.0 Hubble rate in km/s/Mpc; zero disables the flow
hubble_flow_discover bool false Request dataset discovery; currently unresolved and fails during runtime setup
hubble_flow_at_z float 0.0 Snapshot redshift for sightline Hubble integration
omega_matter float 0.272 Matter density parameter for sightline Hubble integration
omega_lambda float 0.728 Dark-energy density parameter for sightline Hubble integration
hubble_mode string constant Sightline integration mode: constant or integrated; aliases constant_slope and integrated_z
seed uint32 121567 Transport RNG seed; inherited by the emission model unless overridden
mpi_same_seed bool false Use the same transport RNG initialization on every MPI rank; does not control source RNGs
use_peeling bool false Enable peeling photons
peel_merge_dist float 0 Peel merge distance in box units; squared in simulation precision, zero disables
peel_merge_dfreq number 0 Signed wavelength offset in Å, converted to an absolute merge-frequency tolerance; zero disables
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.
save_inputphotons bool true Write the input photon stream; false disables input packet buffering and file creation
write_inputphotons_restframe bool true Keep input photon frequencies in the emitter rest frame; false records the launch direction's frame shift
photon_fields string list Standard original columns Columns for raw original photon output; [] selects defaults
photon_fields_peel string list Standard peel columns Columns for raw peel output; ignored when peeling is disabled; [] selects defaults
photon_fields_input string list Standard input columns Columns for raw input output; ignored when input saving is disabled; [] selects defaults

hubble_flow_at_z, omega_matter, omega_lambda, and hubble_mode are accepted but do not affect MCRT calculations. MCRT transport uses hubble_flow directly; output distance conversions use the dataset cosmology.

Output policy is validated before runtime setup. At output initialization, overwrite: false rejects a nonempty destination before removing files. Set overwrite: true deliberately when reusing an output directory. Compression, processor variants and convergence options retain their separate settings.

Simulation restart (mcrtsimulation.restart: true) is currently unsupported and is rejected during preflight. Existing output files remain intact, regardless of their redshift law or whether they contain law metadata.

Source seeds follow mcrtsimulation.seed → emissionmodel.seed → the selected generator’s seed, with each explicit override taking priority. Source seeds must be integers from 0 through 4294967295. Overridden emission seeds and seeds for inactive generators are ignored. The driver seed remains validated for transport.

emissionmodel.mode is required and must be singlesource, listsource, datasetsource, or planesource. Source selection and the effective seed are validated before dataset initialization; YAML key names and MPI source streams are unchanged.

Peel merging requires a single observer. Either nonzero merge threshold enables it, including a negative peel_merge_dist whose square gives the distance threshold. Despite its legacy name, peel_merge_dfreq is in Å; its signed wavelength offset is converted to an absolute frequency difference. Both flat merge keys are validated even when peeling is disabled. Non-finite or unrepresentable thresholds and incompatible observer counts fail preflight.

use_atau0nl: false initializes the nonlocal opacity cache to zero unless an explicit populate mapping supplies AtimesTau0_NL. Such a mapping also takes precedence when computation is enabled.

Transport

Stepping

Spatial propagation keys remain flat under mcrtsimulation. Their values are validated before initialization. After conversion to simulation precision, dist_max must be finite and a positive value must retain a finite, nonzero square. Zero remains an explicit zero cap. Dataset support for automatic limits and periodic-ray stopping conditions are checked during runtime setup.

Hubble flow

The Hubble settings also remain flat under mcrtsimulation. Their values must be finite and representable in simulation precision; nonzero values must remain nonzero after conversion. The rate is converted from km/s/Mpc to s⁻¹ in double precision before narrowing. hubble_flow_discover: true conflicts with an explicit hubble_flow, including zero. Discovery currently has no dataset resolver and fails during initialization; supply the rate explicitly.

With hubble_flow enabled, MCRT defaults to the linear law ν(s) = ν_emit − ν₀ H s/c, where s is accumulated physical path length. To opt into fractional redshifting, ν(s) = ν_emit × exp(−H s/c), build with:

./build.sh --no-clean --cmake-arg -DTHOR_MCRT_FRACTIONAL_HUBBLE=ON

Use OFF to return an existing build to the linear law. This is a CMake option, not a YAML setting. The same selection applies to opacity, scattering, final-flight output, continuum targeting and the max_dlambda distance limit. Output files record mcrt_hubble_redshift_law as linear or fractional.

Both laws keep H, the physical box scale, gas properties and stellar emission fixed. Fractional redshifting defines a frozen-background approximation; it does not evolve the cosmological scale factor or the snapshot.

The stored photon frequency offset is referenced to the local cosmological frame at its most recent emission or scattering. For doublets, the primary line defines this stored offset; both components see the same absolute photon frequency and its selected Hubble shift in opacity and scattering. Peculiar and thermal Doppler shifts still use the interacting component's wavelength. Each scattering incorporates the redshift over the entire incoming flight, including travel across periodic boundaries, before starting a new flight. Peeling photons start in the same frame; use dlambda_peel_shifted to include the redshift over their final flight when analysing their output.

Termination

Distance limits

max_dlambda derives a flight-distance cutoff from the redshift of a line-centre photon; it is not a per-photon wavelength termination condition. Zero disables this proxy. Signed offsets retain the legacy absolute-distance interpretation and must resolve a positive wavelength and absolute frequency for the selected line. Wavelength inputs are converted from Å to cm in double precision before narrowing to the simulation precision.

When this proxy is active, original photons use the smaller of its distance limit and an enabled dist_max. Peeling photons apply peeling.dist_max independently in the same way. Negative distance caps are disabled; zero remains an explicit zero cap.

Wavelength cutoff

For a cutoff at the packet's actual wavelength, use:

mcrtsimulation:
  pbc: true
  hubble_flow: 70  # km/s/Mpc
  termination:
    redward_dlambda_angstrom: 10  # illustrative margin; check convergence

Omitting termination.redward_dlambda_angstrom disables the cutoff. The offset and Hubble flow must both be positive and finite. Source packets finish at λ₀ + the configured offset in the local cosmological frame, including the current flight's Hubble shift. For doublets, λ₀ is the primary line's wavelength. Scattering before the endpoint starts a new flight with the outgoing frequency. Packets already beyond the cutoff finish without further transport, but still contribute emission and direct peels. The cutoff is independent of the injected wavelength range and spatial termination.mode. Choose the margin by checking that further redward transport does not appreciably change the result.

THOR clips the last step before evaluating opacity and marks completed packets with FREQUENCY_LIMIT. Their stored dfreq references the last emission or scattering; use shifted output fields to include the final flight. The cutoff uses the selected linear or fractional Hubble law. A non-finite cutoff flight distance aborts the run with INVALID_FREQUENCY_LIMIT. Other enabled stopping conditions also apply, including dist_max and the legacy max_dlambda distance proxy. Omit these distance caps to stop on frequency alone.

Set peeling.redward_dlambda_angstrom independently to integrate emission and scattering peels into the red wing, using the source cutoff's frame and Hubble law:

mcrtsimulation:
  pbc: true
  hubble_flow: 70  # km/s/Mpc
  termination:
    redward_dlambda_angstrom: 60
  use_peeling: true
  peeling:
    redward_dlambda_angstrom: 60  # illustrative margin; check convergence
    dist_max: -1                # disable the peel distance cap

The offset and Hubble flow must be positive and finite. Omit max_dlambda and disable peeling.dist_max to avoid stopping before resonance; peeling.tau_max still allows early removal. Increase the redward margin until the result converges.

For periodic IFUs, use spectral.frame: box_center to bin the launch event. Binning the endpoint wavelength would pile up flux at the cutoff.

Spatial termination

mcrtsimulation.termination limits transport to a cube or sphere, in box coordinates.

Parameter Default Description
mode none none, cube, or sphere
box required for cube [[xmin, xmax], [ymin, ymax], [zmin, zmax]]
center [0.5, 0.5, 0.5] Sphere center
radius required for sphere Positive sphere radius

Geometry must be finite and remain nondegenerate in the simulation's floating-point precision. Fields for an unselected mode are ignored with a warning.

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 peel-ray travel 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
redward_dlambda_angstrom float absent (disabled) Stop peel rays at this wavelength offset redward of the primary line, in Å
fanout_at_scatter bool true Generate directional peel photons at scattering; false uses Path B conversion
max_peel_in_flight integer 8000000 Path B in-flight budget; zero retains the same default fallback
drain_transport_skip bool false Pause source transport while draining Path B
drain_high_frac float 0.9 Pressure fraction at which draining starts
drain_low_frac float 0.5 Pressure fraction at which source transport resumes
healpix_nside integer absent HEALPix RING observers; power of two in [1, 64]
lines_of_sight list of vectors [[0, 0, 1]] Between 1 and 65535 unit vectors; mutually exclusive with healpix_nside

Omitting peeling, using an empty mapping, or using a null section uses the defaults. A negative dist_max disables the distance limit. An empty observer list is an error. When use_peeling: false, a supplied peeling section is ignored with a warning.

max_peel_in_flight and drain_transport_skip apply only with fanout_at_scatter: false. Drain thresholds additionally require drain_transport_skip: true. Supplying these settings outside their active mode produces a warning and their values are ignored.

Stop conditions and watchdogs

MCRT uses separate stop reasons for ordinary work limits, navigation recovery, and watchdog events.

Reason Meaning MCRT handling
BUDGET_EXHAUSTED Per-photon launch budget reached debug.step_budget_mode, default off (resume)
NAVIGATION_RETRY Geometry requests ownership reinitialization Return to host and reinitialize; independent of budget/watchdog settings
NO_SCATTER_LIMIT Consecutive steps without scattering reached the configured cap stuck_photon_mode, default error
NAVIGATION_FAILED Voronoi navigation exhausted its recovery path Log photon diagnostics and abort
GEOMETRY_STALLED A geometry detector reports exhausted progress recovery Log photon diagnostics and abort
IONIZING_INVALID_PACKET_METADATA Invalid ionizing packet metadata Log photon diagnostics and abort

The two configurable policies accept off, warn, finish, and error. warn logs and permits continuation; finish terminates affected photons and continues the run; error aborts the run. Fatal transport failures always abort, even when a coincident boundary marked the packet finished. These checks run before configurable dispositions or host boundary validation.

mcrtsimulation:
  stuck_photon_mode: error
  max_steps_without_scatter: 1000000
  debug:
    step_budget_mode: off

The no-scatter counter resets on scattering and at each kernel launch. It can count positive-distance steps, so reaching the cap is a watchdog event rather than proof of zero spatial progress. Peel photons never scatter, making this a consecutive-step cap for them. Budget exhaustion also occurs in legitimate thick scatterers: finish can bias results, and error can abort valid runs.

Set max_steps_without_scatter: 0 or stuck_photon_mode: off to disable the no-scatter detector. A cap at or above nsteps_per_photon_max clamps to one less than that budget; if the budget is at most one, detection is disabled. These adjustments and inactive settings produce warnings. This does not disable explicit navigation failures or the persistent MCRT zero-length detector. The latter reports GEOMETRY_STALLED after 64 consecutive zero-length transport steps across kernel relaunches and MPI transfers. Positive travel or an actual interaction resets its per-photon counter; peel-buffer backpressure does not count as a transport step. This guard always aborts and is independent of the two configurable policies.

The remaining mcrtsimulation.debug controls are:

Parameter Default Effect
finish_on_spawn false Immediately finish newly emitted photons; for debugging only.
nkernels_stuck_threshold — Deprecated and ignored with a warning. Optional nkernels storage remains a diagnostic launch count.

An explicit step_budget_mode overrides the deprecated stuck_photon_print and stuck_photon_finish keys. Otherwise, stuck_photon_finish: true selects finish; if it is false or absent, stuck_photon_print: true selects warn. Photon tracing remains configured separately under mcrtsimulation.trace.

Photon tracing

Tracing controls live under mcrtsimulation.trace and require a build with THOR_DEBUG_PHOTONTRACE=ON.

Parameter Default Effect
blocksize 0 Trace entries per block; zero disables tracing.
overhead_factor 1 Extra blocks as a fraction of the photon count; zero allocates no extra blocks.

Active block sizes must fit the device index type. The overhead factor must be finite and nonnegative. It is ignored with a warning when blocksize: 0; both values are ignored with warnings when tracing was not compiled in. Allocation limits that depend on the launch size are checked at runtime.

Timing

These options belong under mcrtsimulation. Durations are in seconds.

Key Default Meaning
initial_kerneltime_probe false Start with a smaller launch to measure kernel time
initial_kerneltime_probe_nphotons 1000 Positive photon limit for that probe
kernel_execution_target_time 900 Soft target duration for adaptive kernel step budgets
runtime_max 0 Run duration limit; zero means unlimited
use_slurm_timelimit true Use the TIMELIMIT environment variable when present
runtime_slurm_safetymargin 600 Seconds reserved before the scheduler deadline

The kernel target must be finite and nonnegative. Zero retains the minimum adaptive step budget; it does not disable transport. Probe counts are ignored with a warning when probing is disabled; the scheduler margin is likewise ignored when use_slurm_timelimit: false.

When enabled and present, TIMELIMIT overrides runtime_max. It must contain whole seconds and exceed the safety margin. Runtime setup computes the effective limit as TIMELIMIT - runtime_slurm_safetymargin; --validate-config checks YAML inputs only. Run limits are checked between steps, so they are not hard deadlines.

Memory budgets

These keys belong under mcrtsimulation:

Key Default Meaning
memory_frac_photons 0.05 Device-memory share for source photons
memory_frac_photons_peel 0.1 Device-memory share for peel photons
memory_peelfactor 30 Static peel seed slots per source photon
memory_peelsize_dynamic_factor 2 Additional seed slots as a multiple of the static allocation
memory_npeel_min_alloc 1000000 Minimum number of peel seed slots

Fractions must be finite and within [0, 1]; allocation counts are nonnegative integers. The peel factor must fit in uint32_t. With fanout_at_scatter: true, it is raised during setup to fit all observers, including when configured as zero. With fanout_at_scatter: false, it must be positive. Zero remains valid for the dynamic factor and minimum allocation. Runtime checks enforce the allocation's index limits once the launch size is known.

The four peel-only options are ignored with a warning when use_peeling: false.

Resonant physics

Line names

Use line.name or the compact linename spelling. Both may be present with the same value; conflicting names are rejected. A partial line: section can accompany linename to set phase or deuterium options.

line.phase_function: auto uses the existing atomic default. An explicit lya phase requires a Lyα transition. The deprecated line.isotropic_phasefunc cannot accompany phase_function; its historical false behavior selects the Lyα phase only for the exact name Lya and isotropic scattering for other names.

A positive line.deuterium_fraction enables coupled H/D transfer and disables core skipping. It requires hydrogen Lyα and a single-line or doublet resonant interactor. The ratio must be finite, nonnegative and representable in the simulation precision. With deuterium disabled, ResonantDoubletInteractor requires the exact primary name MgII. These checks run during preflight; atomic constants and scattering algorithms are unchanged.

The line name selects the atomic properties used for transport. Density and emissivity recipes are selected separately. Supported short names include:

Abbreviation Line Notes
Lya Lyman-alpha (H I 1215.67 Å)
OVI O VI (1031.912 Å) Stronger doublet component
CIV C IV (1548.187 Å) Stronger doublet component
SiIV Si IV (1393.755 Å) Stronger doublet component
MgII Mg II doublet (2796 + 2803 Å) Use with ResonantDoubletInteractor for doublet treatment
MgII_2 Mg II (2803.5324 Å) Second doublet component
FeXXV Fe XXV (1.85 Å)
FeXXV_w Fe XXV w (1.8504 Å) Alias of FeXXV
FeXXV_x Fe XXV x (1.855301 Å) No verified physical phase function; falls back to isotropic scattering with a warning
FeXXV_y Fe XXV y (1.859483 Å)
FeXXV_z Fe XXV z (1.868185 Å)
FeXXV_HeBeta Fe XXV Heβ (1.573181 Å)
OVII O VII (21.60 Å)

Higher Lyman lines use their table labels: Lyb, Lyc, Lyd, Lye, then Ly6 to Ly39 for levels 7p to 40p. Their upper level np also decays to lower s and d levels, so the Voigt damping uses its total radiative width Γ_np. A scattered packet returns in the same line with probability A(np → 1s)/Γ_np, for example 0.882 for Lyβ; otherwise it leaves the transported line, and the cascade products are not followed. Widths and per-absorption cascade outcomes come from src/LymanCascadeTableGenerated.h, generated by scripts/gen_lyman_cascade_header.py. Sightline absorption from tau: blocks and from thor.absorption uses the same widths.

Ion density

By default, Lyman lines use HIdensity; the listed metal ions use their existing cloudydensity_<ion> fields. Existing field aliases still apply. Loading another provider does not change these defaults.

Select a different recipe explicitly, for example with the AtomDB tables:

mcrtsimulation:
  line:
    name: FeXXV_w
    density_field: atomdbdensity_FeXXV

The dataset must configure the selected recipe. Gadget, Tipsy and octree loaders request it during setup. Explicit selections require matching species metadata and physical number-density units, \(\mathrm{cm}^{-3}\). Cloudy, AtomDB and derived HI recipes provide this metadata; raw snapshot fields without it cannot be selected explicitly. Old field caches may need regeneration. Emission fields remain independently selected by the source configuration.

Full line aliases such as H 1 1215.67A and registered NIST labels such as CIV 1548 select the same density defaults as their species. Each NIST entry retains its own atomic constants. Species without a default require an explicit field. Arbitrary wavelength spellings and Cloudy emissivity blends such as Blnd 2798.00A are not transport-line selectors.

A missing required density stops initialization. Synthetic setups can retain density_check_lenient: true when Density already contains the absorbing population. This flag never replaces a missing or incompatible explicitly selected field. Do not combine line.density_field with populate.Density.

Outputs record the resolved line, atomic-data source, population approximation, and selected density field. Species and recipe provenance are also recorded when available. Legacy fields without species metadata retain their existing contract with a warning. Transport keeps its existing approximation of using the ion density as the absorbing population.

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

xcrit sets a minimum only for Laursen09; supplying it with none or Smith15 produces an inactive-setting warning. It must be finite. Negative values clamp to zero with a warning. The deprecated xcrit: -1 also selects Smith15 when acc_scheme is absent or empty; use acc_scheme: Smith15 and omit xcrit instead.

Core skipping is disabled for destructive lines and the doublet interactor; explicit acceleration settings then produce a warning during preflight.

Interactors

Value Description
SimpleInteractor Basic resonant scattering
ResonantWithDustInteractor Includes dust absorption
ResonantDoubletInteractor Doublet lines (e.g., MgII)
LymanSeriesInteractor H I Lyman series with radiative cascades; see below

Lyman-series transport

LymanSeriesInteractor transports Lyα together with the higher H I Lyman lines. It requires linename: Lya: photon frequencies keep the Lyα reference, and each higher line sits at its fixed offset. A packet blueward of Lyβ can pass through the higher resonances, scatter in them, or be converted to a lower Lyman line.

mcrtsimulation:
  linename: Lya
  interactor: LymanSeriesInteractor
  lyman_series:
    n_max: 20       # highest upper level np; 2 transports Lyα only
    tau_tol: 1.0e-8 # skip a line whose per-step optical-depth bound is lower
    eps_rel: 1.0e-4 # skip a non-nearest line below this fraction of the nearest line
Key Type Default Description
n_max int 20 Highest upper level np, from 2 to 40
tau_tol float 1e-8 Absolute per-step optical-depth bound for including a line
eps_rel float 1e-4 Bound relative to the nearest line's per-step optical depth

Each step evaluates Lyα and the lines that can matter: the line nearest in velocity, if the photon is in its core or its Lorentzian-wing bound exceeds tau_tol, and other lines that also exceed eps_rel times the nearest line's optical depth. Paths that only meet Lyα therefore reproduce SimpleInteractor and its random-number sequence.

An interaction selects the absorbing line by its share of the opacity. Lyα events behave as with SimpleInteractor, including core skipping. An absorption into np with n ≥ 3 draws one outcome of the radiative cascade from src/LymanCascadeTableGenerated.h:

  • resonant return in the same line, coherent in the atom frame and isotropic;
  • conversion to a lower Lyman line m, emitted isotropically at line centre in the frame of the absorbing atom;
  • decay through 2s by two-photon emission, which absorbs the packet.

Balmer and other non-Lyman cascade photons are not transported. Peels of higher-line events follow the sampled outcome, as isotropic emission from the absorbing atom.

Build with -DTHOR_MCRT_FRACTIONAL_HUBBLE=ON for Hubble flow. The linear law redshifts every photon at the Lyα rate, which biases higher-line optical depths high by ν_n/ν_Lyα; THOR warns at startup in that case. Resolve each resonance crossing within a cell by limiting max_step so that the Hubble shift per step is a fraction of the Doppler parameter. continuum_advance is not supported.

Resonant scattering phase functions

mcrtsimulation.line.phase_function selects the unpolarized angular distribution for resonant scattering and peeling. The default, auto, selects:

Line Phase function
Lya (including its curated alias) Frequency-dependent Lyα core/wing prescription
FeXXV / Fe25 1.85040A (w resonance line) Rayleigh
MgII / MgII_2 (2796 / 2803 Å) Atom-frame frequency-dependent doublet interference
Lines without a verified default Historical isotropic fallback, with a warning
mcrtsimulation:
  line:
    name: FeXXV
    phase_function: auto

Explicit choices are isotropic, rayleigh, half-rayleigh, and lya (Lyα only). An omitted setting and auto both use isotropic scattering when no verified physical default is known, with a warning identifying the fallback. This preserves historical behavior; it does not establish isotropic scattering as the physical default. Explicit choices are respected. THOR warns if you override the physical default or if no physical default is known for the line. For the MgII doublet, an explicit override applies to both components.

MgII follows the scalar interference phase of Seon (2023), Eqs. 8–9, with the line-center limits described by Dijkstra & Loeb (2008), Appendix A. The sampled ion velocity sets the atom-frame frequency for scattering and peeling; polarization is not propagated.

Migrating existing configurations

The default changed from isotropic to auto. Set phase_function: isotropic to preserve isotropic scattering.

The deprecated isotropic_phasefunc override still accepts true for isotropic scattering. With false, it selects Lyα only for the literal Lya name and isotropic scattering otherwise. Do not specify both keys.

Deuterium scattering

Set mcrtsimulation.line.deuterium_fraction to include neutral deuterium in Lyα transfer. The value is the number ratio nDI / nHI; it defaults to 0 (disabled). For example:

mcrtsimulation:
  interactor: SimpleInteractor
  line:
    name: Lya
    deuterium_fraction: 3e-5

THOR multiplies the neutral hydrogen density by this ratio. Deuterium shares hydrogen's temperature, bulk velocity, and turbulent broadening; its larger mass gives a narrower thermal profile at 1215.34 Å (about 82 km/s blueward of H Lyα). Scattering and peeling use the selected isotope. Phase overrides apply to both.

Supported interactors are SimpleInteractor, ResonantDoubletInteractor, and ResonantWithDustInteractor (to include dust). The fraction must be finite, nonnegative, and representable in the build's precision. Enabling deuterium disables core skipping, increasing runtime at high optical depth. Output records deuterium_fraction; frequency offsets remain relative to H Lyα.

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], finite and > 0       (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: Gadget and Tipsy point clouds and RAMSES/ENZO/ART octrees use the same top-level model selection and parameters. All three recipes honor field_filters.DustAbsorptionCoefficient on the final opacity. Explicit model requests cannot fall back to the legacy nH × Z recipe; legacy sigma_per_H_solar/z_solar keys remain supported but cannot be mixed with modern model parameters in one dust block. Ordinary shellmodel accepts constant, with opacity confined to the shell; laursen and colt are rejected. infiniteslab also accepts constant; its legacy infiniteslab.tau_dust key still works and takes precedence over dust.k_dust_value.

Survival estimator

mcrtsimulation.survival_cutoff controls packet survival in dust interactions and resonant lines with radiative branching:

  • -1 (default): stochastic destruction. The packet scatters at unchanged weight with the survival probability; otherwise it is removed.
  • 0: survival weighting. Multiply the packet weight by the survival probability and scatter.
  • A positive finite value: survival weighting with Russian roulette below the cutoff. A packet of weight w survives with probability w/cutoff and takes the cutoff weight; otherwise it is removed. This preserves the expected weight while reducing work on low-weight packets.

For dust, the survival probability is the albedo. For a resonant line, it is the probability of radiative decay back into that transition.

The cutoff is an absolute packet weight. For example, survival_cutoff: 1.0e-5 enables roulette below that weight. Other negative values, infinity, and NaN are rejected.

Alternatively, mcrtsimulation.survival_mode accepts StochasticDestruction (equivalent to -1) or SurvivalWeighting (equivalent to 0). Specify only one of survival_mode and survival_cutoff.

For conservative lines without a dust interactor, these settings have no effect; explicitly supplying either produces a warning during preflight.

Emission models

Single source

mcrtsimulation:
  emissionmodel:
    mode: "singlesource"
    singlesource:
      nphotons: 1000
      lum_total: 1e42
      position: [0.5, 0.5, 0.5]

nphotons, lum_total, and position are required. The photon count is a positive global budget distributed across MPI ranks. Luminosity is finite and nonnegative, in erg/s; positions use dataset user coordinates and may lie outside the unit cube. A positive forced_weight overrides the luminosity-derived packet weight; zero or negative values leave it active.

Omit direction, or set it to [0, 0, 0], for isotropic emission. A nonzero three-vector selects directed emission and is normalized during setup. With that direction, cone_half_angle_deg selects a cone from 0 to 180 degrees; omitting it or using a negative value gives a collimated beam. Cone settings without a direction are ignored with a warning.

Preflight validates these inputs before opening output files or initializing sources. It checks finite values, array sizes, and whether luminosity, packet weights and direction normalization can be represented in the build's floating precision. Dataset geometry and MPI-dependent photon-ID capacity remain runtime checks.

List source

This example uses Lya_StellarParticle_Luminosity for stellar Lyα emission from a cosmological Gadget snapshot with stellar formation times and masses. Replace /data/snapshot with your snapshot path.

mcrtsimulation:
  linename: Lya
  emissionmodel:
    mode: listsource
    listsource:
      loader: gadget
      gadget:
        path: /data/snapshot
        particle_type: PartType4
        luminosity_field: Lya_StellarParticle_Luminosity
      nphotons_per_unit_lum: 100
      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

Plane source

mode: planesource places equal-luminosity emitters at the cell centers of a rectangular grid. Each emitter radiates isotropically. Set these options under emissionmodel.planesource:

Parameter Type Default Description
center vec3 [0.5, 0.5, 0.5] Plane center in dataset user coordinates.
normal_axis integer 2 Axis perpendicular to the plane: 0, 1, or 2.
size_u float 1.0 Signed extent along (normal_axis + 1) % 3.
size_v float 1.0 Signed extent along (normal_axis + 2) % 3.
n_u integer 50 Positive grid count along u.
n_v integer 50 Positive grid count along v.
lum_total float Required Finite nonnegative luminosity in erg/s, divided equally among the grid emitters.

Negative extents reverse sampling order; zero extents collapse that grid axis. The generator clips emitters to its ownership box and applies the shared source filters below. MCRT checks geometry and luminosity conversion before allocation; coordinates must remain finite in the build's precision, grid counts must be positive without overflowing their product, and positive luminosity must remain nonzero per emitter.

Multi source options

For listsource, datasetsource, and planesource, nphotons_per_unit_lum sets packets per luminosity unit (normally \(10^{42}\) erg/s). Each positive source receives its luminosity times this rate, rounded up and bounded by nphotons_per_source_min and nphotons_per_source_max. The rate and minimum default to 1. target_nphotons is no longer accepted; ionizing RT uses ionizing_rt.nphotons.

Parameter Type Default Description
nphotons_per_source_min integer 1 Minimum packets per emitter with positive luminosity; zero is allowed. Must not exceed the maximum.
nphotons_per_source_max integer Progress-counter limit Positive maximum packets per emitter, bounded by the progress-counter and index types.
nphotons_per_unit_lum float 1.0 Positive packets per native luminosity unit.
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.
restart_state_file string empty Load rank-local emitter progress and RNG state from a checkpoint.
restart_state_output_file string empty Save emitter progress and RNG state on finalization; independent of the input path.
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.

MCRT validates budgets and luminosity cuts before loading the catalog. Ratios must be finite, positive, and within the build's normal floating-point range. Cuts must be finite; a fractional cut must be at most 1, with negative values disabling it. Only an explicitly supplied absolute cut participates in the looser policy.

Spatial filters are also validated before loading the catalog. Cube bounds must remain finite and satisfy min < max in simulation precision; the minimum is included and the maximum excluded. A sphere includes its boundary and defaults to center [0.5, 0.5, 0.5]. Its radius must be positive with a finite, nonzero square in simulation precision. Geometry fields for other modes are ignored with a warning; unknown keys inside spatial_filter are errors.

Restart paths are checked as strings during preflight; checkpoint files are opened at runtime. Each {rank} in either path is replaced by the MPI rank. With multiple ranks and no placeholder, .rank<N> is appended. Empty paths disable the corresponding operation. These checkpoints resume source emission; they do not enable full simulation restart.

Dataset source

datasetsource creates emitters from the dataset's Emissivity field and cell volumes. Load this field with the dataset or select it through mcrtsimulation.populate.Emissivity.

mcrtsimulation:
  emissionmodel:
    mode: "datasetsource"
    datasetsource:
      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
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. Supported for unigrid (one draw per axis) and pointcloud_voronoi (200-step hit-and-run sampling). Other geometries raise an error.

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.

MCRT validates the emission_position choice before dataset loading. Inactive dataset-source positioning is ignored with a warning. The concrete dataset's support for uniform_in_cell is checked during source preparation, before allocating emitters.

Analytic emission spectra

emissionmodel.spectrum accepts the shapes delta, gaussian, doublegaussian, uniform, and gaussian_with_continuum. Omission selects a zero-shift delta. The default constant recipe reads parameters from spectrum.constant; thermal requires a Gaussian and reads temperature in Kelvin.

The default global scope requires the selected recipe subsection. For a Gaussian mixture, mus and sigmas must each contain two entries. The optional weights defaults to [0.5, 0.5]; a global empty weight list also means equal weights. local scope allows an omitted recipe subsection, uses zero-valued fallback parameters and equal mixture weights, and applies the recipe per emitter. Local thermal recipes read emitter temperatures at runtime.

Velocities are in km/s, and equivalent width is in Angstrom. Preflight checks selectors, array sizes, finite values, nonnegative widths and temperatures, ordered uniform edges, and nonnegative mixture weights with a finite positive sum in the sampler's precision. The existing velocity limit is c/2, counting Gaussian tails to ten widths. Global continuum spectra require positive equivalent width; nonpositive local equivalent width disables the continuum.

The legacy scalar syntax, such as spectrum: gaussian with a sibling gaussian_spectrum: {mu: 0, sigma: 100}, remains supported. Unknown active parameter keys are errors; supplied inactive settings produce warnings.

Packet weights

An MCRT packet weight is a photon rate in line units. One unit is the photon rate of \(10^{42}\) erg/s at the line rest frequency ν0, i.e. \(10^{42}\) erg/s / (hν0). Frequency changes from scattering and Hubble flow preserve this photon rate. Absorption and survival weighting change it; peel contributions also include angular weighting and attenuation toward the observer.

Emitter luminosities and spectral shapes describe energy: a spectrum is L_ν in \(10^{42}\) erg/s, for lines and continua alike, as for BPASS L_λ. At launch, a packet emitted at ν_rest receives its luminosity share times ν0/ν_rest, so its rest-frame energy is that share. A line at ν0 keeps its share exactly; a Doppler-broadened line changes by about σ/c (1e-4 at 30 km/s). Spectral velocities are in km/s and may reach at most c/2 (150,000 km/s, counting Gaussians to 10σ); setup rejects larger offsets, which usually mean a unit slip.

Energy outputs multiply each contribution by ν_recorded/ν0. Binned outputs already include this conversion, even with units: weight; that setting omits physical calibration, not frequency conversion. The recorded frequency depends on the output:

  • histogram1d, histogram2d and apertureSpectrum use the endpoint frequency for dlambda_peel_shifted, the box-centre frequency for dlambda_boxcenter, and the stored frequency for any other quantity;
  • sbmap uses its cube's frame. Without a cube it uses the box-centre frequency, which does not depend on how far peels fly; on the original stream it uses the stored frequency. The image attribute energy_frequency names the choice;
  • intrinsic apertureSpectrum and sbmap outputs on the input stream use each observer's frequency. Intrinsic sbmap outputs need a configured unit line_of_sight or peeling.

A histogram2d whose two axes record different frequencies is rejected, for example dlambda_peel_shifted against dlambda_boxcenter or stored dlambda. The photon attributes energy_factor, energy_factor_peel_shifted and energy_factor_boxcenter give these factors. Raw photon output stores the unscaled weight. Convert it with photons/s = weight × \(10^{42}\)/(hν0) and erg/s = weight × \(10^{42}\) × (ν0 + dfreq)/ν0. Output files record mcrt_packet_weight: photon_rate, mcrt_weight_unit_erg_s and mcrt_weight_reference_frequency_hz. Outputs from earlier THOR versions lack these attributes; their weights kept each packet's launch energy. The Python analysis guide shows how to read both conventions and calculate a photon escape fraction or luminosity.

Native BPASS stellar continuum settings

Sample a stellar continuum directly from the native BPASS spectra:

mcrtsimulation:
  emissionmodel:
    mode: listsource
    bpass_continuum:
      enabled: true                              # default: false
      spectra_path: /data/bpass-spectra.h5        # required when enabled
      wavelength_range_angstrom: [1025.72, 1215.67]  # default: direct Lyα pumping band
    listsource:
      loader: gadget
      nphotons_per_unit_lum: 100
      gadget:
        path: /data/snapshot
        particle_type: PartType4
        mass_has_h_factor: true                  # TNG, including z = 0

This opt-in mode requires listsource. Omit emissionmodel.spectrum and luminosity_field: the native bpass-spectra-v1 file supplies both. It is separate from the derived-quantity bpass_table_path.

Wavelengths are in the star's rest frame, before velocity and Hubble shifts. The finite bounds must satisfy 0 < minimum < maximum and lie inside the native grid. Configuration validation keeps them in double precision without opening the file. Disabled settings are reported and ignored; unknown keys are errors.

Use the non-legacy Gadget stellar loader (PartType4, or PartType1 where stars are stored there). It requires formation scale factors, total metallicity, initial masses and stellar Velocities; missing initial mass is an error. Wind particles are excluded. Formation times rounded just past the snapshot at their stored precision give age zero; larger overshoots are rejected. Ages require a flat matter-plus-Λ cosmology: abs(Omega0 + OmegaLambda - 1) <= 1e-6. The loader converts masses and physical peculiar velocities, including Gadget's square-root-of-scale-factor convention. For TNG at z = 0, explicitly set mass_has_h_factor: true (its z = 0 default is false).

The reader loads only the band and its neighboring samples. The sampler clips boundary intervals to the exact limits and shares template CDFs across stars. It interpolates linear luminosity with weights from log age and log metallicity, then selects a neighboring template in proportion to interpolation weight × photon rate. Per-star caches retain float64 physical rates through catalog cuts; band luminosities use the generator's 1e42 erg/s units.

Packets sample photon number, proportional to λ Lλ. The allocator's luminosity weight is multiplied by \(h\nu_0 / (L/Q)\) to obtain the stored photon-rate weight, where \(\nu_0\) is the reference line frequency, L is the band luminosity and Q is the photon rate. With N packets from a source, each represents Q/N photons per second, independent of its sampled wavelength.

Energy outputs multiply the stored weight by \(\nu_\mathrm{recorded}/\nu_0\), as described in Packet weights. At emission in the source rest frame, this gives the allocator's luminosity weight multiplied by E / (L/Q), where E = hν is the sampled photon energy. The resulting luminosities sum to L statistically. Set nphotons_per_unit_lum to control sampling, subject to per-source rounding and limits.

Launch, emission peeling and intrinsic aperture spectra/IFU cubes use stellar velocities; scattering uses gas velocities. Stellar emission and intrinsic records are completed on the source rank before MPI handoff. Received packets resume transport without stellar lookups. Emission Doppler shifts use each sampled frequency to first order in v/c. SimpleInteractor treats higher Lyman resonances as transparent; LymanSeriesInteractor transports them with cascades (see Lyman-series transport). BPASS sources can opt into the stellar continuum launch shortcut described below.

Stellar continuum launch shortcut

For a stellar continuum near Lyα, an optional preprocessing kernel skips the initial flight to the entry face of the periodic image containing the configured blueward margin. It runs once per fresh emission batch, after the source Doppler shift and emission records have been captured.

mcrtsimulation:
  continuum_advance:
    enabled: true       # default: false
    margin_kms: 2000    # nonnegative blueward velocity offset; default: 2000

This option treats the configured singlesource or listsource population as stellar emission. It supports BPASS continuum and the analytic uniform and gaussian_with_continuum spectra, applying to packets above the margin. It requires Lya, SimpleInteractor, positive finite Hubble flow, and a fully periodic unit box. Spatial termination surfaces and dust interactors are unsupported. A finite, nonnegative margin is required; packets below it use ordinary transport. For u = c × (ν/ν₀ − 1), the target distance in physical units depends on the build's redshift law:

  • Default linear (THOR_MCRT_FRACTIONAL_HUBBLE=OFF): s = (u − margin)/H.
  • Fractional (THOR_MCRT_FRACTIONAL_HUBBLE=ON): s = (c/H) × ln[(c + u)/(c + margin)].

Here u, margin and c use the same velocity units. The jump stops at the entering face of the periodic image containing that point.

The packet keeps its direction, weight, optical-depth target and stored frequency. The skipped distance is added to lsp_dist, so normal opacity and scattering account for the Hubble shift once. Source records and emission peel seeds remain at the star. Restored packets and MPI relaunches do not trigger the shortcut. Destinations on another rank enter the existing communication queue before local transport. A skip that reaches the incoming-flight distance cutoff is declined.

The skipped flight is assumed transparent: the margin alone does not bound its Lyα wing optical depth. Check convergence against larger margins and a run with the shortcut disabled before using it for production results.