Units and conventions

Tengri carries no astropy.units-style runtime tagging. Every array is a plain JAX array in a fixed, documented unit; conversions live as pure functions in tengri.units.

This page lists the unit each layer expects, the conventions for metallicity / SFR / time, and the conversion helpers you’ll reach for most often.

The conventions, in one table

Quantity

Unit

Where it shows up

Wavelength

Å, vacuum

wave_obs, wave_rest, filter curves, line catalogues

Spectral flux density F_ν

erg s⁻¹ cm⁻² Hz⁻¹

observed photometry / spectroscopy

Spectral luminosity L_ν

erg s⁻¹ Hz⁻¹

every SEDComponent.apply output

Bolometric luminosity

erg s⁻¹ (preferred) or L_sun

AGN agn_log_lbol = log10(L_bol/L_sun)

Time / age

yr (internal), Myr / Gyr (user-facing)

SFH age grids, PSD timescales

SFR

M_sun yr⁻¹

SFH outputs, predict_sfh

Stellar mass

M_sun

mass normalisations

Metallicity (SSP grid)

log10(Z) absolute

DSPS grid axis

Metallicity (user API)

log10(Z/Z_sun)

met_logzsol, with LOG10_ZSUN = -1.848 offset

Magnitudes

AB system

photometry helpers default to AB

Distance modulus

mag

distance_modulus_from_dl

Redshift

dimensionless

redshift parameter

Vacuum wavelengths throughout — including emission lines (H_alpha = 6564.61 Å). Never use air wavelengths. If you need them, convert at the boundary:

from tengri.units import vacuum_to_air
wave_air = vacuum_to_air(wave_vac)  # Å -> Å

Where unit boundaries live

Unit translations happen at three places. Everywhere else the unit is fixed:

  1. Parameters → forward model. User-facing names (psd_tau_myr, agn_log_lbol, met_logzsol) are translated to internal units (years, erg/s, log10(Z) absolute) by the internal_param_map on each registered component. The user only sees the friendly unit; the model only sees the internal one.

  2. Forward model → observation. SEDModel.predict_* returns flux in the unit declared by the Observation — F_ν cgs by default.

  3. Observation → user analysis. Use the conversions below to take F_ν cgs to Jy, AB mag, maggies, etc., for plotting or comparison with catalogue data.

Conversion helpers

All in tengri.units and JAX-native (JIT/grad/vmap-safe):

from tengri import units

# Spectral flux density
units.fnu_to_jy(fnu)             # erg/s/cm²/Hz -> Jansky
units.fnu_to_ujy(fnu)             # ... -> microjansky
units.fnu_to_njy(fnu)             # ... -> nanojansky
units.fnu_to_maggies(fnu)         # ... -> SDSS maggies
units.jy_to_fnu(jy)               # inverse
units.fnu_to_flambda(fnu, wave)   # F_ν -> F_λ (erg/s/cm²/Å)

# AB / Vega magnitudes
units.fnu_to_ab_mag(fnu)
units.ab_mag_to_fnu(mag_ab)
units.ab_to_vega(mag_ab, band)    # band name from filter registry
units.vega_to_ab(mag_vega, band)

# L_ν ↔ F_ν at a given luminosity distance
units.lnu_to_fnu(lnu, d_lum_mpc, redshift)
units.fnu_to_lnu(fnu, d_lum_mpc, redshift)

# Absolute / apparent / surface brightness
units.absolute_to_apparent(M_abs, dm)
units.apparent_to_absolute(m_app, dm)
units.distance_modulus_from_dl_mpc(d_lum_mpc)

# Wavelength media
units.air_to_vacuum(wave_air)
units.vacuum_to_air(wave_vac)

# Energy
units.lsun_to_erg_per_s(lsun)
units.erg_per_s_to_lsun(ergs)

# Optical depth ↔ attenuation
units.tau_to_attenuation(tau)     # = exp(-tau)
units.attenuation_to_tau(att)

The full list (about 35 helpers) is tengri.units.__all__from tengri import units; help(units) shows it.

Why no astropy.units

Tengri is JAX-native and pre-1.0 research code. Wrapping every array in an astropy.units.Quantity would (a) break JIT, since Quantity is not a JAX type, (b) double the memory of every internal array, and (c) introduce subtle conversion edge cases at every vmap boundary.

The convention here — fixed unit per layer, conversion helpers at the boundary — has been stable since v0.1 and matches what working SED-fitting codes (Bagpipes, Prospector, FSPS) actually do under the hood. If you need to interoperate with astropy.units at the user layer, convert your inputs to plain arrays in tengri’s units before the fit and tag the outputs after.

Common gotchas

  • agn_log_lbol is always log10(L_bol / L_sun) at the API level. The AGN component converts to erg/s internally; user code should never multiply by 3.828e33 itself.

  • PSD timescale is Myr as psd_tau_myr at the API level, years as psd_tau_yr internally. The internal-param map handles the 1e6 factor.

  • Metallicity offset. met_logzsol is log10(Z/Z_sun) (user) but the SSP grid is log10(Z) absolute. The translation adds LOG10_ZSUN = -1.848, defined in tengri.utils.physics_constants. Do not reproduce this constant by hand.

  • Emission lines are vacuum throughout. H_alpha = 6564.61 Å, not 6562.8 Å (which is air).

  • All SED components return erg/s/Hz (standardised 2026-04-08). If you’re porting an external component, normalise to this unit before returning.

  • Per-band photometry units. predict_photometry returns one F_ν value per filter band, in cgs. The filter integral cancels the per-Hz-per-Å distinction; the output is mean F_ν over the bandpass.

See also