Skip to content

C++ Bindings

The thor-bindings package exposes a subset of THOR's C++ routines to Python via nanobind. The current focus is on basic building blocks — line profile evaluation, scattering phase functions, emission line setup, and atomic data lookup. Executing or querying full THOR simulations directly from Python is planned for the future.

Installation

The bindings are compiled from C++ source and require the same build environment as THOR itself (see Installation). From the repository root:

uv pip install -e python/thor-bindings

Modules

All modules are lazy-loaded to avoid import conflicts:

from thor.bindings import core, profiles, physics, atomic

core — Fundamental Types

Vector types, bounding boxes, and physical constants.

from thor.bindings import core

# 3D vector (double precision)
v = core.Vec3fp(1.0, 2.0, 3.0)
print(v.x, v.y, v.z)
print(v.norm())
print(v.dot(core.Vec3fp(1, 0, 0)))
w = v.cross(core.Vec3fp(0, 0, 1))

# 2D vector
v2 = core.Vec2fp(1.0, 2.0)

# Bounding box
bb = core.BoundingBox(core.Vec3fp(0, 0, 0), core.Vec3fp(1, 1, 1))
print(bb.is_valid())

# Physical constants (CGS and SI submodules)
print(core.CGS.cval)   # speed of light in cm/s
print(core.CGS.kB)     # Boltzmann constant
print(core.SI.mp)      # proton mass in kg
print(core.pi)
Type Description
Vec3fp 3D vector with x, y, z, dot(), cross(), norm(), norm2(), normalize(), arithmetic operators, indexing
Vec2fp 2D vector with x, y, dot(), norm(), norm2(), normalize(), arithmetic operators
BoundingBox Axis-aligned bounding box with min, max (Vec3fp), is_valid(), invalidate()
CGS Submodule: Msun, cval, mu, mp, me, pc, kpc, Mpc, kB, e, h, llya, Elya
SI Submodule: Msun, cval, mu, mp, me, pc, kB, e, eps0, h

profiles — Line Profiles

Spectral line profile evaluation functions. All are plain functions taking (x, a) and returning a float.

from thor.bindings import profiles

# Voigt profile H(a, x) — default Smith+15 implementation
value = profiles.VoigtProfile(1.0, 4.7e-4)

# Other Voigt implementations
value = profiles.VoigtProfile_Tasitsiomi06(1.0, 4.7e-4)
value = profiles.VoigtProfile_Schreier18(1.0, 4.7e-4)
value = profiles.VoigtProfile_HumlicekW4(1.0, 4.7e-4)

# Gaussian and Lorentz profiles
gauss = profiles.GaussianProfile(1.0, 4.7e-4)
lorentz = profiles.LorentzProfile(1.0, 4.7e-4)

# Core-wing boundary frequency functions (take a only)
xcw = profiles.xcw_s15(4.7e-4)              # Smith+15
xcw = profiles.xcw_b25estrin(4.7e-4)        # Byrohl+25 (Estrin)
xcw = profiles.xcw_b25horner(4.7e-4)        # Byrohl+25 (Horner)
Function Arguments Description
VoigtProfile (x, a) Default Voigt profile (Smith+15)
VoigtProfile_Tasitsiomi06 (x, a) Tasitsiomi 2006 approximation
VoigtProfile_Schreier18 (x, a) Schreier 2018 implementation
VoigtProfile_Schreier18branched (x, a) Schreier 2018 with asymptotic branching
VoigtProfile_HumlicekW4 (x, a) Humlicek W4 algorithm
GaussianProfile (x, a) Pure Gaussian profile
LorentzProfile (x, a) Pure Lorentz profile: \(1/(1+x^2)\)
xcw_s15 (a) Smith+15 core-wing boundary frequency
xcw_s15_fastlog (a) Smith+15 with fast log approximation
xcw_b25estrin (a) Byrohl+25 core-wing boundary (Estrin)
xcw_b25estrin_fastlog (a) Byrohl+25 with fast log (Estrin)
xcw_b25horner (a) Byrohl+25 core-wing boundary (Horner)
xcw_b25horner_fastlog (a) Byrohl+25 with fast log (Horner)

physics — Physical Processes

Emission line setup, photon representation, phase functions, and Lyman-alpha helpers.

from thor.bindings import physics

# Set up an emission line by name
el = physics.EmissionLine()
el.setup("Lya")
print(el.get_lambda0())   # rest wavelength in Angstrom
print(el.get_afactor())   # Voigt damping parameter

# Or set up from atomic parameters
el.setup(lambda0=1215.67, f12=0.4162, A21=6.265e8, Amass=1.008)

# Cross-section and frequency conversions
sigma0 = el.get_sigma0(temperature=1e4)
x = el.dfreq_to_x(dfreq=0.1, vthermal=12.8)
dlambda = el.dfreq_to_dlambda(0.1)

# Photon state
photon = physics.Photon()
print(photon.position)     # Vec3fp
print(photon.direction)    # Vec3fp
print(photon.weight)

EmissionLine

Method Description
setup(linename, verbose=False) Initialize from line name (e.g., "Lya", "CIV", "MgII")
setup(lambda0, f12, A21, Amass, verbose=False) Initialize from atomic parameters
get_lambda0() Rest wavelength in Angstrom
get_nu0() Rest frequency
get_afactor() Voigt damping parameter \(a\)
get_sigma0(temperature) Line-center cross-section at given temperature
get_sigma() Cross-section including Doppler effects
get_local_thermal_velocity(temperature, vel_turb=0.0) Local thermal velocity including turbulence
get_atom_velocity() Atom velocity
scatter() Perform scattering event
dfreq_to_x(dfreq, vthermal) Frequency offset to dimensionless \(x\)
x_to_dfreq(x, vthermal) Dimensionless \(x\) to frequency offset
dlambda_to_freq() Wavelength shift to frequency
dlambda_to_dfreq() Wavelength shift to frequency offset
dfreq_to_dlambda() Frequency offset to wavelength shift

Photon

Attribute Type Description
position Vec3fp Current position
direction Vec3fp Propagation direction
origin Vec3fp Emission position
dfreq float Frequency offset
weight float Luminosity weight
id int Photon ID
state int State flag
current_cell int Current cell index
next_cell int Next cell index
tau_seen float Optical depth accumulated
tau_target float Target optical depth
lsp_dist float Distance to last scattering point
nscatterings int Number of scattering events

Phase Functions (physics.phase)

mu = 0.5  # cosine of scattering angle
pdf = physics.phase.IsotropicPhase_pdf(mu)
pdf = physics.phase.RayleighPhase_pdf(mu)
pdf = physics.phase.GreensteinPhase_pdf(mu, g=0.73)
pdf = physics.phase.LyaCore2P32Phase_pdf(mu)
Function Description
IsotropicPhase_pdf(mu) Isotropic: returns 0.5
RayleighPhase_pdf(mu) Rayleigh (dipole): \(\propto 1 + \mu^2\)
GreensteinPhase_pdf(mu, g=0.73) Henyey-Greenstein with asymmetry parameter \(g\)
LyaCore2P32Phase_pdf(mu) Lya core \(2P_{3/2}\): \(1 + \frac{3}{7}\mu^2\)

Lyman-alpha Helpers (physics.lya)

T = 1e4  # temperature in K
frac = physics.lya.frec_B(T)          # Case B recombination fraction
alpha = physics.lya.alpha_B(T)         # Case B recombination coefficient [cm^3/s]
eps = physics.lya.epsilon_recB(T, ne=1.0, nHII=1.0)  # emissivity [erg/s/cm^3]
Function Arguments Description
frec_A(T) temperature [K] Case A recombination fraction
frec_B(T) temperature [K] Case B recombination fraction
alpha_A(T) temperature [K] Case A recombination coefficient [cm\(^3\)/s]
alpha_B(T) temperature [K] Case B recombination coefficient [cm\(^3\)/s]
Gamma_1s2p(T) temperature [K] Collisional excitation rate coefficient [cm\(^3\)/s]
gamma_1s2p(T) temperature [K] Maxwell-averaged collisional excitation rate [cm\(^3\)/s]
epsilon_recB(T, ne, nHII) T [K], electron/HII densities [cm\(^{-3}\)] Case B recombination emissivity [erg/s/cm\(^3\)]
epsilon_exc(T, ne, nHI) T [K], electron/HI densities [cm\(^{-3}\)] Collisional excitation emissivity [erg/s/cm\(^3\)]

atomic — Atomic Data

Atomic element database, solar abundances, and ion parsing.

from thor.bindings import atomic

# Look up an element by name or symbol
elem = atomic.findElement("Hydrogen")
print(elem.number)           # 1
print(elem.symbol)           # "H"
print(elem.mass)             # atomic mass in AMU
print(elem.solar_abundance)  # n/nH (Grevesse+ 2010)

# Look up by atomic number
fe = atomic.getElement(26)
print(fe.name)               # "Iron"

# Solar mass fractions
print(atomic.SOLAR_X)  # hydrogen
print(atomic.SOLAR_Y)  # helium
print(atomic.SOLAR_Z)  # metals

# Ion parsing
ion = atomic.parseIonName("FeXXV")
print(ion.element_symbol)    # "Fe"
print(ion.ion_number)        # 25

ElementInfo

Returned by findElement() and getElement():

Attribute Description
number Atomic number
name Full element name
symbol Chemical symbol
mass Atomic mass [AMU]
solar_abundance Solar abundance [n/nH] (Grevesse+ 2010)
isotopes Mass numbers of isotopes
ion_energies Ionization energies [eV]

Functions

Function Description
findElement(name_or_symbol) Look up element by name or symbol. Returns None if not found.
getElement(atomic_number) Look up element by Z (1-30). Returns None if not found.
getSolarAbundance(element) Solar abundance [n/nH] by name or symbol
getAtomicMass(element) Atomic mass [AMU] by name or symbol
solarAbundanceToMassRatio(element) Convert solar abundance to mass ratio
parseIonName(ion) Parse ion name (e.g., "FeXXV", "HI") to IonInfo
getElementSymbolFromIon(ion) Element symbol from ion name (e.g., "FeXXV" -> "Fe")
nameToSymbol(name) Element name to symbol (e.g., "Hydrogen" -> "H")
symbolToName(symbol) Symbol to element name (e.g., "H" -> "Hydrogen")
romanToInt(roman) Roman numeral to integer (e.g., "VI" -> 6)
intToRoman(number) Integer to roman numeral (e.g., 6 -> "VI")

Legacy Modules (Deprecated)

For backwards compatibility, three legacy module names are still importable but emit deprecation warnings:

Legacy Module Replacement
thor_lineprofile profiles
thor_emissionline physics
thor_xwing profiles
# Deprecated — triggers DeprecationWarning
from thor.bindings import thor_lineprofile

# Use instead:
from thor.bindings import profiles