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:
Modules
All modules are lazy-loaded to avoid import conflicts:
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 |