# Line Configuration `unite`'s line configuration system is designed to be flexible and feature rich, allowing user to set up complex multi-line models with shared and correlated parameters across a wide range of line profiles. This page walks though the core concepts and usage patterns for building line configurations. --- ## Creating a Line Configuration {class}`~unite.line.LineConfiguration` is the core container that defines which emission lines to fit, their profile shapes, and how parameters are shared between lines. The best way to get started is to create an empty configurations. We'll also need the `prior` module for defining priors on line parameters, and `astropy.units` for specifying line wavelengths. ```python from unite import line, prior from astropy import units as u lc = line.LineConfiguration() ``` --- ## Adding Lines ### Basic Usage ```python lc.add_line('H_alpha', 6563.0 * u.AA) ``` :::{important} Line names must be **unique** within a `LineConfiguration`. Adding a second line with the same name raises a `ValueError`. Use distinct names for different kinematic components, e.g. `'Ha_narrow'` and `'Ha_broad'`. ::: At minimum each line requires: - A **name** (string) — used in results tables and YAML output - A **rest-frame center wavelength** Centers must be {class}`astropy.units.Quantity` with wavelength units: ```python lc.add_line('Ly_alpha', 1216.0 * u.AA, ...) lc.add_line('CO_10', 2.6 * u.um, ...) ``` ### Adding a Single Line Optionally each line can also include: - A relative **redshift** token ({class}`~unite.line.Redshift`) - A **flux** token ({class}`~unite.line.Flux`) - A **profile shape** (e.g. `Gaussian`, `PseudoVoigt`, etc.) - **FWHM token(s)** appropriate for the chosen profile - Additional shape parameters (e.g. `h3`, `h4` for `GaussHermite`) - A **strength** parameter which multiplies the flux token, mostly used for line ratios (see below) If not specified, default priors are used for redshift, flux, and FWHM tokens, and the profile defaults to `Gaussian`. ```python z = line.Redshift('z', prior=prior.Uniform(-0.01, 0.01)) fwhm = line.FWHM('fwhm', prior=prior.Uniform(50, 500)) lc.add_line( 'H_alpha', 6563.0 * u.AA, redshift=z, fwhm_gauss=fwhm, flux=line.Flux('Ha_flux', prior=prior.Uniform(0, 1)), ) ``` You can preview your line configuration by printing it: ``` print(lc) LineConfiguration: 1 lines, 1 flux / 1 z / 1 profile params Name Wavelength Profile Redshift Params Flux Strength ------- ---------------- -------- -------- ------ ------- -------- H_alpha 6563.00 Angstrom Gaussian z fwhm Ha_flux 1.00 Redshift: z Uniform(low=-0.01, high=0.01) Params (fwhm_gauss): fwhm Uniform(low=50.0, high=500.0) Flux: Ha_flux Uniform(low=0.0, high=1.0) ``` :::{note} Redshift tokens/priors are relative to the systemic system redshift which is specified with the {doc}`instrument`. This allows configurations to be redshift-agnostic and reusable across different datasets. ::: ### Adding Multiple Lines `unite` also supports adding multiple lines at once with `add_lines`. Each entry in `centers` becomes an independent line with a unique auto-generated name of the form `'{name}_{center_value}'` (e.g. `'OIII_4960'`, `'OIII_5008'`). You can also supply an explicit `names` array of the same length as `centers`. ```python # Auto-generated names: 'OIII_4960' and 'OIII_5008' lc.add_lines('OIII', [4960, 5008] * u.AA, redshift=z, fwhm_gauss=fwhm) # Explicit names lc.add_lines('OIII', [4960, 5008] * u.AA, names=['OIII_blue', 'OIII_red'], redshift=z) ``` Note how we fix the line ratio here. ```python flux = line.Flux('OIII') lc.add_lines( 'OIII', [4960, 5008] * u.AA, redshift = z, # Same redshift fwhm = [fwhm, fwhm], # Different FWHMs (just as an example) flux = [line.Flux(prior = prior.Fixed(flux / 3)), flux], # Fixed ratio ) ``` However, it is likely easier to specify multiples through the `strength` parameter, which multiplies the flux token when building the model. Both approaches yield the same result. ```python flux = line.Flux('OIII') lc.add_lines( 'OIII', [4960, 5008] * u.AA, flux = line.Flux(), # Default behaviour, this would mean all lines have the same flux strength = [1/3, 1] # But we pass a strength per line which is multiplied by the flux token when building the model ) ``` --- ## Parameter Sharing This is `unite`'s central design pattern. A **token** is a named Python object representing a model parameter. **Same Python object = same parameter in the model.** These are the following token types: | Token | Role | Unit | |-------|------|------| | {class}`~unite.line.Redshift` | Redshift offset from systemic | dimensionless | | {class}`~unite.line.FWHM` | Line width | km/s | | {class}`~unite.line.Flux` | Line flux normalization | internal units | | {class}`~unite.line.LineShape` | Arbitrary profile parameter (h3, h4, etc.) | per-parameter basis | :::{note} Fluxes are relative to a scale computed based on the spectrum. See Flux and Error Scales in {doc}`build_model` for details. ::: ### Shared Kinematics ```python # One redshift, one FWHM → shared across all lines z = line.Redshift('z', prior=prior.Uniform(-0.01, 0.01)) fwhm = line.FWHM('fwhm', prior=prior.Uniform(50, 500)) lc.add_line('H_alpha', 6563.0 * u.AA, redshift=z, fwhm_gauss=fwhm, flux=line.Flux('Ha_flux', prior=prior.Uniform(0, 10))) lc.add_line('NII_6585', 6585.0 * u.AA, redshift=z, fwhm_gauss=fwhm, flux=line.Flux('NII6585_flux', prior=prior.Uniform(0, 10))) lc.add_line('NII_6549', 6549.0 * u.AA, redshift=z, fwhm_gauss=fwhm, flux=line.Flux('NII6549_flux', prior=prior.Uniform(0, 10))) ``` Result: one `z` parameter and one `fwhm` parameter in the model, shared by all three lines. ### Independent Parameters ```python z_narrow = line.Redshift('narrow', prior=prior.Uniform(-0.01, 0.01)) z_broad = line.Redshift('broad', prior=prior.Uniform(-0.01, 0.01)) lc.add_line('Ha_narrow', 6563.0 * u.AA, redshift=z_narrow, ...) lc.add_line('Ha_broad', 6563.0 * u.AA, redshift=z_broad, ...) ``` ### Parameter Names All token classes accept an optional first positional argument that becomes a semantic label for the parameter. **It is highly recommended to name your tokens** — without a name, `unite` auto-generates one (e.g. `fwhm_0`, `redshift_2`) that becomes difficult to interpret in output. A **type-specific prefix is automatically prepended** to your label to form the final site name: - `Redshift('nlr')` → site name `'z_nlr'` (prefix: `z_`) - `FWHM('broad')` → site name `'fwhm_broad'` (prefix: `fwhm_`) - `Flux('Ha')` → site name `'flux_Ha'` (prefix: `flux_`) So pass **only the semantic label**, not the full prefixed name: ```python # Auto-generated names → based on line name, less explicit z = line.Redshift() # Explicit semantic labels → clear, shareable across lines z_nlr = line.Redshift('nlr') # → site name 'z_nlr' z_blr = line.Redshift('blr') # → site name 'z_blr' fwhm = line.FWHM('broad') # → site name 'fwhm_broad' flux = line.Flux('Ha') # → site name 'flux_Ha' ``` The site names show up in: - The `samples` dict returned by `mcmc.get_samples()` - Column names in the parameter table from `make_parameter_table()` :::{tip} Use short, semantic labels (without prefixes) that identify the physical component, e.g. `'nlr'`, `'blr'`, `'broad'`, `'Ha'`, `'NII'`. The type-specific prefix is added automatically. This makes site names concise and easy to navigate in results. ::: Lines can share the same name but must have at least one different token, otherwise an error will be raised. This allows you to easily set up multi-component models with shared parameters. ```python z = line.Redshift('nlr', prior=prior.Uniform(-0.01, 0.01)) fwhm_n = line.FWHM('narrow', prior=prior.Uniform(50, 400)) fwhm_b = line.FWHM('broad', prior=prior.Uniform(500, 3000)) lc.add_line('H_alpha', 6563.0 * u.AA, redshift=z, fwhm_gauss=fwhm_n, flux=line.Flux('Ha_n', prior=prior.Uniform(0, 10))) lc.add_line('H_alpha', 6563.0 * u.AA, redshift=z, fwhm_gauss=fwhm_b, flux=line.Flux('Ha_b', prior=prior.Uniform(0, 10))) ``` --- ## Dependent Priors Prior bounds on FWHM or Flux tokens can reference other tokens, creating dependency chains that are automatically resolved at model-build time. See {doc}`priors` for the full reference. ```python fwhm_narrow = line.FWHM('n', prior=prior.Uniform(50, 500)) fwhm_broad = line.FWHM('b', prior=prior.Uniform(fwhm_narrow + 150, 5000)) ``` This ensures the broad component is always at least 150 km/s wider than the narrow component. --- ## Line Profiles Here we list all currently supported profiles in `unite`. Most profiles are **analytically integrated** over pixels (exact CDF differences) and convolved with the instrumental LSF. The exception is `SkewVoigt`, which uses a midpoint-rule approximation (see below). See {doc}`/concepts` for the LSF convolution convention. Profiles are set via the `profile` argument (case-insensitive strings or class instances): | String | Profile | FWHM Parameter(s) | Shape Parameter(s) | Pixel integration | |--------|---------|-------------------|--------------------|-------------------| | `'gaussian'`, `'normal'` | `Gaussian` | `fwhm_gauss` | | analytic | | `'cauchy'`, `'lorentzian'` | `Cauchy` | `fwhm_lorentz` | | analytic | | `'pseudovoigt'`, `'voigt'` | `PseudoVoigt` | `fwhm_gauss`, `fwhm_lorentz` | | analytic | | `'laplace'`, `'exponential'` | `Laplace` | `fwhm_exp` | | analytic | | `'semg'`, `'exp-gaussian'` | `SEMG` | `fwhm_gauss`, `fwhm_exp` | | analytic | | `'hermite'`, `'gauss-hermite'` | `GaussHermite` | `fwhm_gauss` | `h3`, `h4` | analytic | | `'split-normal'`, `'two-sided'` | `SplitNormal` | `fwhm_blue`, `fwhm_red` | | analytic | | `'skew-normal'`, `'skewnormal'` | `SkewNormal` | `fwhm_gauss` | `alpha` | analytic | | `'skew-voigt'`, `'skewvoigt'` | `SkewVoigt` | `fwhm_gauss`, `fwhm_lorentz` | `alpha` | midpoint rule | | `'boxgauss'`, `'box-gauss'`, `'boxcar'` | `BoxGauss` | `fwhm_box`, `fwhm_gauss` | | analytic | | `'gaussiansplitlaplace'`, `'gaussian-split-laplace'`, `'aemg'` | `GaussianSplitLaplace` | `fwhm_gauss`, `fwhm_l_blue`, `fwhm_l_red` | | analytic | ### Gaussian (default) The simplest and most common profile. A Gaussian intrinsic shape convolved with the Gaussian LSF; the result is also Gaussian with $\mathrm{FWHM} = \sqrt{\mathrm{fwhm\_gauss}^2 + \mathrm{lsf\_fwhm}^2}$. **Parameters:** `fwhm_gauss` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='Gaussian', redshift=z, fwhm_gauss=fwhm, flux=flux) ``` ### PseudoVoigt The Voigt profile — the convolution of a Gaussian and a Lorentzian — computed via two different approximations depending on integration mode: - **Analytic mode** (CDF-based pixel integration): uses the extended pseudo-Voigt approximation of Ida, Ando & Toraya (2000), which decomposes the profile into four components (Gaussian, Lorentzian, intermediate, and Pearson-VII) with sixth-order polynomial mixing coefficients. Achieves < 0.12% peak-height deviation from the true Voigt profile. The Gaussian LSF is added in quadrature to `fwhm_gauss` before computing the approximation parameters. - **Quadrature mode** (PDF evaluation at nodes): uses the exact Voigt profile computed via the Faddeeva function $w(z) = e^{-z^2}\operatorname{erfc}(-iz)$, approximated with the Humlicek (1982) W4 rational scheme (~$10^{-4}$ relative error across the upper half-plane). **Parameters:** `fwhm_gauss` (km/s), `fwhm_lorentz` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='PseudoVoigt', redshift=z, fwhm_gauss=line.FWHM('g', prior=prior.Uniform(50, 500)), fwhm_lorentz=line.FWHM('l', prior=prior.Uniform(0, 500)), flux=flux) ``` ### Cauchy (Lorentzian) A pure Lorentzian profile. Internally a `PseudoVoigt` with `fwhm_gauss = 0`. **Parameters:** `fwhm_lorentz` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='Cauchy', redshift=z, fwhm_lorentz=line.FWHM('fwhm', prior=prior.Uniform(0, 1000)), flux=flux) ``` ### Laplace (Exponential) A double-exponential (Laplace) profile convolved with the Gaussian LSF. **Parameters:** `fwhm_exp` (km/s) ### SEMG — Symmetric Exponentially Modified Gaussian A Gaussian convolved with a Laplace distribution. The Gaussian component (including LSF) contributes exponential wings that are symmetric about the centre. See {doc}`/derivations/semg` for the closed-form CDF derivation. **Parameters:** `fwhm_gauss` (km/s), `fwhm_exp` (km/s) ### GaussHermite A Gaussian modified by Hermite polynomial corrections using the probabilists' convention. `h3` controls skewness; `h4` controls kurtosis. Convolution with the Gaussian LSF rescales the shape parameters as $h_m' = h_m\,(\sigma_g/\sigma_\text{tot})^m$. See {doc}`/derivations/gauss-hermite` for the full derivation. **Parameters:** `fwhm_gauss` (km/s), `h3` (dimensionless), `h4` (dimensionless) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='GaussHermite', redshift=z, fwhm_gauss=line.FWHM('fwhm', prior=prior.Uniform(50, 1000)), h3=line.Param('h3', prior=prior.TruncatedNormal(0, 0.1, -0.3, 0.3)), h4=line.Param('h4', prior=prior.TruncatedNormal(0, 0.1, -0.3, 0.3)), flux=flux) ``` ### SplitNormal A two-sided Gaussian with independent widths on the blue and red sides. **Parameters:** `fwhm_blue` (km/s), `fwhm_red` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='SplitNormal', redshift=z, fwhm_blue=line.FWHM('b', prior=prior.Uniform(50, 500)), fwhm_red=line.FWHM('r', prior=prior.Uniform(50, 500)), flux=flux) ``` ### SkewNormal A Gaussian (with LSF) multiplied by an erf skew factor $[1 + \text{erf}(\alpha_\text{eff}(x-c)/w_0')]$. For `alpha = 0` the profile reduces exactly to a Gaussian. Unlike `SkewVoigt`, the convolution with the Gaussian LSF is **exact** — the shape parameter rescales analytically as $\alpha_\text{eff} = \alpha\sigma_g / \sqrt{\sigma_g^2 + (1+\alpha^2)\sigma_\text{lsf}^2}$, with no numerical correction. Pixel integration uses the closed-form skew-normal CDF $\Phi(z) - 2T(z, \alpha_\text{eff})$ via Owen's T function. See {doc}`/derivations/skew-normal` for the full derivation. **Parameters:** `fwhm_gauss` (km/s), `alpha` (dimensionless) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='SkewNormal', redshift=z, fwhm_gauss=line.FWHM('fwhm', prior=prior.Uniform(50, 500)), alpha=line.LineShape('alpha', prior=prior.TruncatedNormal(0, 2, -10, 10)), flux=flux) ``` ### SkewVoigt A pseudo-Voigt profile multiplied by a skew factor $[1 + \text{erf}(\alpha(x-c)/w_0)]$ where $w_0 = \Gamma_V/(2\sqrt{\ln 2}) = \sigma_V\sqrt{2}$ is the erf scale derived from the Thompson et al. Voigt FWHM $\Gamma_V$. For a pure Gaussian ($\Gamma_l = 0$) this reduces to the standard skew-normal with shape parameter $\alpha$ and dispersion $\sigma_g$. The profile integrates to 1 for any `alpha` because the skew factor is odd and the pseudo-Voigt is even. Convolution with the Gaussian LSF rescales the skewness to an effective $\alpha_\text{eff}$ (see {doc}`/derivations/skew-voigt`). :::{warning} `SkewVoigt` is **not** analytically integrated over pixels. The pixel integral of the skew correction requires Owen's T function (Gaussian part) and a separate quadrature (Lorentzian part) — neither reduces to standard functions. Instead, `unite` evaluates the profile at each pixel midpoint and multiplies by the pixel width. This is accurate when the profile is well-resolved (intrinsic FWHM several times the pixel width), but introduces sub-pixel quadrature error for marginally resolved lines. Consider using `integration_mode='convolution'` as an alternative integration mode. ::: **Parameters:** `fwhm_gauss` (km/s), `fwhm_lorentz` (km/s), `alpha` (dimensionless) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='SkewVoigt', redshift=z, fwhm_gauss=line.FWHM('g', prior=prior.Uniform(50, 500)), fwhm_lorentz=line.FWHM('l', prior=prior.Uniform(0, 500)), alpha=line.LineShape('alpha', prior=prior.TruncatedNormal(0, 2, -10, 10)), flux=flux) ``` ### BoxGauss A uniform rectangular (boxcar) distribution convolved with a Gaussian. The intrinsic profile is constant across a velocity window of width `fwhm_box` and zero outside it (area = 1). Convolution with the combined Gaussian (LSF ⊕ `fwhm_gauss` in quadrature) smooths the sharp edges. The exact pixel integral is computed analytically using the antiderivative of the Gaussian CDF. The profile reduces to a pure Gaussian as `fwhm_box → 0`, and to a sharp rectangular window as `fwhm_gauss → 0` (with `lsf_fwhm → 0`). **Parameters:** `fwhm_box` (km/s), `fwhm_gauss` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='BoxGauss', redshift=z, fwhm_box=line.FWHM('box', prior=prior.Uniform(100, 2000)), fwhm_gauss=line.FWHM('g', prior=prior.Uniform(0, 500)), flux=flux) ``` ### GaussianSplitLaplace (Asymmetric EMG) A Gaussian (with LSF) convolved with a split-Laplace (asymmetric double-exponential) distribution, where the blue (short-wavelength) and red (long-wavelength) exponential tails are controlled independently. The exact pixel integral is computed analytically via the closed-form antiderivative of the Gaussian–split-Laplace CDF. **Parameters:** `fwhm_gauss` (km/s), `fwhm_l_blue` (km/s), `fwhm_l_red` (km/s) ```python lc.add_line('H_alpha', 6563.0 * u.AA, profile='GaussianSplitLaplace', redshift=z, fwhm_gauss=line.FWHM('g', prior=prior.Uniform(50, 500)), fwhm_l_blue=line.FWHM('lb', prior=prior.Uniform(0, 500)), fwhm_l_red=line.FWHM('lr', prior=prior.Uniform(0, 500)), flux=flux) ``` --- ## Merging Configurations {class}`~unite.line.LineConfiguration` supports merging two configurations: ```python lc_narrow = line.LineConfiguration() # ... add narrow lines ... lc_broad = line.LineConfiguration() # ... add broad lines ... # strict=True (default, also __add__): raises on token name collisions lc_combined = lc_narrow + lc_broad # strict=False: shares same-named tokens of same type lc_combined = lc_narrow.merge(lc_broad, strict=False) ``` The `strict=False` mode is useful when both configurations define tokens with the same name and you want them to be treated as the same model parameter. However, proceed with caution, it will not check that the priors for the same-named tokens are identical, it will choose the token from the first configuration. --- ## Absorption Lines `unite` supports absorption lines alongside emission lines. Absorption profiles produce a wavelength-dependent transmission `T(λ) = exp(-τ₀ · φ_norm(λ))` that multiplies the emission and/or continuum flux, where `τ₀` is the {class}`~unite.line.Tau` parameter and `φ_norm(λ) = φ(λ) / φ(λ_center)` is the profile normalized to 1 at the nominal line center. The key difference from emission lines is that absorption lines use a {class}`~unite.line.Tau` (optical depth) token instead of a {class}`~unite.line.Flux` token. :::{warning} **Two distinct approximations apply to absorption lines — one mode-dependent, one universal.** **Analytic mode only:** each profile is integrated over pixels independently before the nonlinear transmission is applied, computing `exp(-τ·∫φ)` rather than `∫F·exp(-τ·φ)`. This is accurate when the absorber is well-resolved but introduces an approximation for unresolved or marginally resolved lines. Use `integration_mode='convolution'` to avoid this. **Analytic mode:** the absorption profile `φ(λ)` passed to `exp(-τ·φ)` is the LSF-convolved profile, not the intrinsic one. The correct observable requires convolving the nonlinear product `F·exp(-τ·φ_intrinsic)` with the LSF over its full multi-pixel support. This approximation is accurate when the absorber is **resolved** (intrinsic FWHM ≫ LSF FWHM) or **optically thin** (τ ≪ 1). For **unresolved, optically thick absorbers** the inferred τ will be biased high and the curve of growth misrepresented. Convolution mode eliminates both approximations. See {ref}`lsf-pre-convolution-of-absorption-profiles` for a full discussion. ::: ### Adding Tau-Parametrized Lines Tau-parametrized lines use the same profile shapes as flux-parametrized lines. The distinction is made by passing a `Tau` token instead of `Flux`. Any profile can be used — the profile controls the shape, `tau` vs `flux` controls how it enters the model: ```python # Gaussian absorption (single FWHM parameter) lc.add_line('HI_abs', 6563.0 * u.AA, tau=line.Tau()) # Voigt absorption (Gaussian + Lorentzian FWHM) lc.add_line('HI_voigt', 4861.0 * u.AA, profile='voigt', tau=line.Tau()) # Lorentzian absorption (single Lorentzian FWHM) lc.add_line('HI_lor', 4341.0 * u.AA, profile='cauchy', tau=line.Tau()) ``` ### Tau vs Flux The `Tau` parameter is the **optical depth at the nominal line center** of the intrinsic (pre-LSF) profile — i.e. `τ₀ = τ(λ_center)` where `λ_center` is the rest-frame center wavelength shifted by the line's redshift. Concretely: - `τ₀ = 1` → transmission ≈ 37% at line center (optically thin to moderate) - `τ₀ = 3` → transmission ≈ 5% (significantly optically thick) - `τ₀ ≫ 1` → effectively black at line center **For symmetric profiles** (Gaussian, PseudoVoigt, Cauchy, Laplace, SEMG, SplitNormal), `τ₀` is also the *maximum* optical depth anywhere in the profile. **For asymmetric profiles** (GaussHermite with `h3 ≠ 0`, SkewVoigt with large `alpha`), the profile peak shifts away from the nominal center wavelength. `τ₀` remains the optical depth *at the nominal center*, not the absolute maximum the profile reaches. If the physical peak depth matters for your science case, prefer a symmetric profile for absorption lines, or account for this distinction when interpreting posteriors. ```python # Explicit tau token with custom prior tau = line.Tau('deep', prior=prior.Uniform(0, 50)) lc.add_line('HI_abs', 6563.0 * u.AA, tau=tau) ``` If neither `flux` nor `tau` is specified, the line defaults to emission (with an auto-created `Flux` token). Passing both `flux` and `tau` raises `TypeError`: ```python # This raises TypeError: lc.add_line('bad', 6563.0 * u.AA, flux=line.Flux(), tau=line.Tau()) # Error! ``` ### Sharing Tau Parameters Like `Flux`, `Redshift`, and `FWHM` tokens, `Tau` tokens can be shared across multiple absorption lines to produce a single model parameter: ```python tau = line.Tau('balmer_tau') lc.add_line('HI_Ha', 6563.0 * u.AA, tau=tau) lc.add_line('HI_Hb', 4861.0 * u.AA, tau=tau) ``` ### Mixing Emission and Absorption Emission and absorption lines coexist naturally in the same configuration: ```python lc = line.LineConfiguration() lc.add_line('Ha', 6563.0 * u.AA) # emission (Gaussian by default) lc.add_line('HI_abs', 6563.0 * u.AA, tau=line.Tau()) # absorption ``` ### Depth Ordering (`zorder`) Each line carries an integer `zorder` that determines which tau absorbers attenuate it. A tau absorber at depth `Z` only absorbs components with `zorder < Z` — i.e. sources *behind* it. **Defaults** (no arguments required for the common foreground-screen geometry): | Line type | Default `zorder` | |---|---| | Emission (`Flux`) | `0` | | Absorption (`Tau`) | `1` | With these defaults every tau absorber sits in front of all emission lines. Pass `zorder` explicitly to `add_line()` to break from the default geometry: ```python # Absorber at zorder=1 (default) — absorbs Ha (zorder=0) but not AGN (zorder=2) lc = line.LineConfiguration() lc.add_line('Ha', 6563.0 * u.AA) # emission, zorder=0 lc.add_line('AGN_Ha', 6563.0 * u.AA, zorder=2) # emission, zorder=2 - not absorbed lc.add_line('NaD', 5893.0 * u.AA, tau=line.Tau()) # absorber, zorder=1 (default) ``` The `zorder` column is visible when you print the configuration: ``` Name Wavelength Profile Redshift Params Flux/Tau zorder Strength ------- ---------- -------- -------- ------ -------- ------ -------- Ha 6563.00 Gaussian z fwhm ha 0 1.00 AGN_Ha 6563.00 Gaussian z fwhm ha_agn 2 1.00 NaD 5893.00 Gaussian z fwhm tau_nad 1 1.00 ``` See {ref}`component-depth-ordering-zorder` in the model-building guide for the full mapping from `zorder` values to physical geometry, including how to reproduce the three classic absorber-position scenarios. --- ## Serialization {class}`~unite.line.LineConfiguration` supports standalone YAML serialization: ```python lc.save('lines.yaml') lc2 = line.LineConfiguration.load('lines.yaml') ``` Token sharing is preserved: if two lines shared a `Redshift` token before saving, they will share the same reconstructed token after loading. See {doc}`serialization` for the full workflow and YAML format.