MCRT Analysis
The thor.mcrt module (part of thor-rt) provides Python classes for loading and analyzing Monte Carlo Radiative Transfer output.
PhotonMCRT
Top-level entry point that loads a THOR config file and discovers all available photon datasets.
from thor.mcrt import PhotonMCRT
mcrt = PhotonMCRT("config.yaml")
print(mcrt.datasets.keys()) # e.g., dict_keys(['input', 'peel', 'original'])
Constructor
| Parameter | Description |
|---|---|
outputpath |
Path to the THOR configuration YAML file |
overwrite |
Overwrite cached dataset files |
The constructor reads config["mcrtsimulation"]["outputpath"], then scans for input/, peel/, and original/ subdirectories. Each valid subdirectory becomes a PhotonMCRTDataset entry in datasets.
Attributes
| Attribute | Type | Description |
|---|---|---|
config |
dict |
Full parsed YAML configuration |
datasets |
dict[str, PhotonMCRTDataset] |
Photon datasets keyed by type ("input", "peel", "original") |
PhotonMCRTDataset
Represents a single photon dataset (one of input, peel, or original). Inherits from scida's DatasetWithUnitMixin for lazy Dask-backed HDF5 loading with unit support.
ds = mcrt.datasets["peel"]
print(ds.photon_type) # "peel"
print(ds.linename) # "Lya"
print(ds.lambda0) # 1215.67
print(ds.cosmological) # True
Constructor
PhotonMCRTDataset(
outputpath: str,
overwrite: bool = False,
config: str | dict | None = None,
default_los: int = 0,
photon_type: str | None = None,
**kwargs
)
| Parameter | Description |
|---|---|
outputpath |
Path to the photon type subdirectory (e.g., output/peel) |
overwrite |
Overwrite cached dataset files |
config |
YAML config path or dict. Enables unit conversion and cosmological features. |
default_los |
Index into peeling.lines_of_sight for the default line of sight |
photon_type |
One of "input", "peel", "original". Auto-detected from directory name if omitted. |
Note
When used through PhotonMCRT, the constructor is called automatically with the correct config and photon_type.
Key Attributes
| Attribute | Type | Description |
|---|---|---|
photon_type |
str |
"input", "peel", or "original" |
cosmological |
bool |
Whether the simulation is cosmological |
redshift |
float |
Redshift (0.0 for non-cosmological) |
hubble_flow |
float |
Hubble flow in km/s/Mpc |
linename |
str |
Emission line name (e.g., "Lya", "MgII") |
lambda0 |
float |
Rest-frame wavelength in Angstrom |
los |
ndarray or None |
Line-of-sight direction vector (shape (3,)) |
losdirs |
ndarray or None |
LOS rotation matrix (shape (3, 3)) |
length_unit |
pint.Quantity |
Physical length per code unit (e.g., "35000.0 ckpc/h") |
domain |
ndarray |
Domain bounds, shape (3, 2): [[x_min, x_max], ...] |
center |
ndarray |
Domain center in box coordinates |
center_los |
ndarray |
Domain center in LOS coordinates |
data |
dict-like | Dask-backed field accessor (see Available Fields) |
return_projection
Create a 2D surface brightness map by binning photon positions along the line of sight.
projection, extent = ds.return_projection(nbins=512, zoomfactor=2.0)
# With units
projection, extent, units_str = ds.return_projection(return_units=True)
# units_str: "erg s⁻¹ cm⁻² arcsec⁻²"
| Parameter | Type | Default | Description |
|---|---|---|---|
nbins |
int or None |
auto | Pixel resolution. Defaults to ngrid from config or 512. |
img_size |
float or None |
auto | Image size in code units. Defaults to domain extent. |
img_center |
ndarray or None |
auto | Image center in LOS coordinates. Defaults to domain center. |
zoomfactor |
float |
1.0 |
Zoom factor (divides img_size) |
return_mccounts |
bool |
False |
Return raw photon counts instead of physical units |
return_units |
bool |
False |
Append units string to return tuple |
Returns: (projection, extent) or (projection, extent, units_str).
projection: 2D array of shape(nbins, nbins)in surface brightness unitsextent:[x_min, x_max, y_min, y_max]in physical kpc, centered on the domain
return_datacube
Create a 3D volumetric histogram of photon positions.
cube, ex, ey, ez = ds.return_datacube(nbins=128)
# In redshift space
cube, ex, ey, ez = ds.return_datacube(
use_redshift_space=True,
unitstr="luminosity_density"
)
| Parameter | Type | Default | Description |
|---|---|---|---|
nbins |
int or list[int] |
128 |
Bins per dimension |
ranges |
ndarray or None |
auto | Min/max per dimension in code units, shape (3, 2) |
unitstr |
str |
"luminosity_density" |
Unit type: "luminosity_density", "luminosity", or "none" |
use_los |
bool |
True |
Bin in LOS coordinates |
use_redshift_space |
bool |
False |
Shift z-axis by spectral offset (requires use_los=True) |
return_units |
bool |
False |
Append units string |
Returns: (datacube, edges_x, edges_y, edges_z) with edges in physical kpc.
return_spectrum
Extract a spectrum within a circular aperture.
hist, bin_centers = ds.return_spectrum(
center_los=ds.center_los,
aperture_radius_arcsec=2.0,
bins=np.linspace(-10, 10, 100)
)
| Parameter | Type | Default | Description |
|---|---|---|---|
center_los |
ndarray |
required | Center position in LOS coordinates (shape (3,)) |
aperture_radius_arcsec |
float |
required | Aperture radius in arcseconds |
bins |
ndarray or None |
linspace(-10, 10, 100) |
Bin edges for the wavelength axis (in Angstrom offset) |
Returns: (hist, bin_centers) — histogram counts and bin center positions.
Other Methods
| Method | Returns | Description |
|---|---|---|
is_cosmological() |
bool |
Whether the simulation has cosmological settings |
get_cosmology_and_redshift() |
(Cosmology, float) |
Astropy Planck15 cosmology and redshift |
get_boxsize_and_hubblefactor() |
(float, float) |
Box size in ckpc/h and Hubble parameter \(h\) |
pos_to_los_pos(pos) |
ndarray |
Rotate box coordinates to LOS frame |
los_pos_to_pos(pos_los) |
ndarray |
Rotate LOS coordinates back to box frame |
arcsec_per_unitlength() |
float |
Angular scale at the simulation redshift |
dlambda_to_vel(dlambda) |
float |
Convert wavelength shift to velocity in km/s |
Available Fields
Fields are accessed via ds.data["fieldname"] and return Dask arrays.
Stored Fields
| Field | Shape | Units | Description |
|---|---|---|---|
position |
(N, 3) |
code length | Photon position |
direction |
(N, 3) |
dimensionless | Photon direction |
weight |
(N,) |
\(10^{42}\) erg | Photon luminosity weight |
weight_peel |
(N,) |
\(10^{42}\) erg | Peeling weight (peel datasets only) |
dlambda |
(N,) |
Angstrom | Wavelength shift from line center |
tau_seen |
(N,) |
dimensionless | Optical depth accumulated |
tau_target |
(N,) |
dimensionless | Target optical depth |
column_density_seen |
(N,) |
cm\(^{-2}\) | Column density traversed |
nscatterings |
(N,) |
dimensionless | Number of scattering events |
nsteps |
(N,) |
dimensionless | Number of simulation steps |
state |
(N,) |
dimensionless | Photon state flag |
current_cell |
(N,) |
dimensionless | Current cell index |
origin |
(N, 3) |
code length | Emission position |
id |
(N,) |
dimensionless | Photon ID |
lsp_dist |
(N,) |
code length | Distance to last scattering point |
dlambda_peel_shifted |
(N,) |
Angstrom | Peeling-shifted wavelength offset (peel datasets only) |
next_cell |
(N,) |
dimensionless | Next cell index |
Derived Fields
Computed on-the-fly from stored fields:
| Field | Description |
|---|---|
position_los |
Position rotated to LOS frame |
position_rs_los |
Position in redshift space (LOS frame) |
lsp_los |
Last scattering position in LOS frame (backtracked by lsp_dist for peel photons) |
Example: Projection with Matplotlib
import matplotlib.pyplot as plt
from thor.mcrt import PhotonMCRT
mcrt = PhotonMCRT("config.yaml")
ds = mcrt.datasets["peel"]
projection, extent, units = ds.return_projection(
nbins=512, zoomfactor=1.0, return_units=True
)
fig, ax = plt.subplots()
im = ax.imshow(
projection.T,
origin="lower",
extent=extent,
cmap="inferno",
)
ax.set_xlabel("x [kpc]")
ax.set_ylabel("y [kpc]")
plt.colorbar(im, label=units)
plt.show()
Unit Conversion
Physical units are computed using utilities from thor.common.units:
from thor.common.units import get_units
import astropy.units as u
# Luminosity (10^42 erg/s per photon weight)
units = get_units("luminosity", cosmology, z)
# Surface brightness (area must be an astropy Quantity)
pixel_area = (0.01 * u.Mpc) ** 2
units = get_units("surface_brightness", cosmology, z, area=pixel_area)
# Other unit types: "flux", "luminosity_density"
Box size and redshift are auto-detected from the config file using thor.common.boxsize:
- First checks the dataset section for
boxsize(in cm), converts to physical kpc - Falls back to HDF5 header attributes from the simulation snapshot
- Zoom factor from
gadget.zoom_boxorgadget.zoom_targetis applied whenconsider_zoom=True