diff --git a/doc/arts/concept.absorption.lbl.rst b/doc/arts/concept.absorption.lbl.rst
index 1dd247bb38..33d6ab61a8 100644
--- a/doc/arts/concept.absorption.lbl.rst
+++ b/doc/arts/concept.absorption.lbl.rst
@@ -324,6 +324,38 @@ where :math:`\rho` is the number density of the absorbing species,
where VMR is the volume-mixing ratio of the absorbing species.
+.. list-table::
+ :header-rows: 1
+
+ * - Parameter
+ - Description
+ * - :math:`\rho`
+ - Number density of the absorbing isotopologue, :math:`\mathrm{VMR} \cdot P / (kT)`
+ * - :math:`\mathrm{VMR}`
+ - Volume-mixing ratio of the absorbing species
+ * - :math:`P`
+ - Atmospheric pressure
+ * - :math:`c`
+ - Speed of light
+ * - :math:`\nu`
+ - Sampling frequency
+ * - :math:`\nu_0`
+ - Line centre frequency
+ * - :math:`h`
+ - Planck constant
+ * - :math:`k`
+ - Boltzmann constant
+ * - :math:`T`
+ - Temperature
+ * - :math:`g_u`
+ - Degeneracy of the upper state
+ * - :math:`E_l`
+ - Energy of the lower state
+ * - :math:`Q(T)`
+ - Partition function at temperature :math:`T`
+ * - :math:`A_{lu}`
+ - Einstein A coefficient for spontaneous emission
+
.. _lbl-nlte:
Non-local thermodynamic equilibrium
@@ -404,7 +436,7 @@ Zeeman effect
-------------
If Zeeman effect is considered, the emission and absorption terms above are modified by quantum number state distribution.
-For O :sub:`2`, for example, this introduces a factor of
+For O\ :sub:`2`, for example, this introduces a factor of
.. math::
@@ -422,4 +454,615 @@ It can be `computed using software `_ s
Line-mixing using Error-corrected Sudden
****************************************
-TBD
+When the atmosphere is at sufficient pressure, collisions occur frequently enough that
+absorption lines of a vibrational-rotational band can no longer be treated
+independently. Molecules undergoing collisions may exchange rotational angular
+momentum, transferring population between rotational levels. At intermediate
+pressures this introduces off-diagonal couplings between lines in a spectral band,
+leading to the phenomenon of *line mixing*. At high pressures the lines collapse
+towards a pressure-broadened Q-branch.
+
+The Error-corrected Sudden (ECS) approximation provides a rigorous, quantum-mechanical
+framework for computing this mixing. The "Sudden" part refers to the
+Infinite-Order-Sudden (IOS) approximation, in which the collision time is assumed short
+compared with the rotational period. The "Error-corrected" part refers to the
+subsequent rescaling of the relaxation matrix elements to satisfy the first-order
+optical sum rule exactly, removing a systematic bias that arises from the sudden
+approximation.
+
+.. _lbl-ecs-lineshape:
+
+ECS Line Shape
+==============
+
+For a band of :math:`n` interacting absorption lines, the ECS complex absorption shape
+for a single broadening species (see :ref:`lbl-ecs-multispecies` for the full
+expression) is written in terms of the complex relaxation matrix :math:`\mathbf{W}` as
+
+.. math::
+
+ \chi(\nu) \propto \mathrm{Im}\left[\mathbf{d}^T \left(\nu \mathbf{I} - \mathbf{W}\right)^{-1} \mathbf{p}\, \mathbf{d}\right],
+
+where :math:`\mathbf{d}` is the vector of reduced dipole matrix elements (one entry per
+line), :math:`\mathbf{p}` is the diagonal matrix of lower-state thermal populations
+(:math:`p_j = g_j\exp(-E_l^{(j)}/kT)/Q(T)`, the Boltzmann fractional population of
+the lower state of line :math:`j`, following the :ref:`lbl-lte` notation where
+:math:`g_j` is the lower-state degeneracy and :math:`Q(T)` the partition function),
+and :math:`\mathbf{W}` is the full complex :math:`n \times n` relaxation matrix whose
+diagonal and off-diagonal elements are described in :ref:`lbl-ecs-relaxmat`.
+
+The relaxation matrix is diagonalised as :math:`\mathbf{W} = \mathbf{V} \tilde{\boldsymbol{\nu}} \mathbf{V}^{-1}`,
+where :math:`\tilde{\boldsymbol{\nu}}` is the diagonal matrix of complex
+*equivalent line* positions. Each equivalent line :math:`k` has a complex
+frequency :math:`\tilde{\nu}_k` (real part: position, imaginary part: pressure
+broadening) and a complex *equivalent strength* :math:`\tilde{S}_k`.
+
+Explicitly, the equivalent strength for line :math:`k` is
+
+.. math::
+
+ \tilde{S}_k = \left(\sum_j d_j V_{jk}\right) \left(\sum_j p_j d_j V^{-1}_{kj}\right),
+
+and the ECS line shape function is
+
+.. math::
+
+ F_{ECS}(\nu) = \frac{\sqrt{\ln 2}}{\sqrt{\pi}} \sum_k \tilde{S}_k \frac{w(z_k)}{G_{D,k}},
+
+where :math:`w` is the Faddeeva function and
+
+.. math::
+
+ z_k = \frac{\left(\tilde{\nu}_k - \nu\right)\sqrt{\ln 2}}{G_{D,k}}, \qquad
+ G_{D,k} = G_D^{fac} \cdot \mathrm{Re}\!\left[\tilde{\nu}_k\right],
+
+with the Doppler scale factor
+
+.. math::
+
+ G_D^{fac} = \sqrt{\frac{2000 R T}{m c^2}},
+
+where :math:`R` is the ideal gas constant in J mol\ :sup:`-1` K\ :sup:`-1`,
+:math:`m` the molar mass in g mol\ :sup:`-1`, :math:`c` the speed of light, and
+:math:`T` the temperature (same symbols as in the plain LBL definition in
+:ref:`lbl-line-shape`).
+
+Note that :math:`G_{D,k}` is formed by multiplying :math:`G_D^{fac}` by
+:math:`\mathrm{Re}[\tilde{\nu}_k]` — the real part of the :math:`k`-th eigenvalue
+— rather than by any original line centre :math:`\nu_{0,j}`. This is necessary
+because eigenvalue decomposition does not in general return eigenvalues in the same
+order as the input lines, so there is no well-defined mapping from equivalent line
+:math:`k` to a single physical line :math:`j`. Using :math:`\mathrm{Re}[\tilde{\nu}_k]`
+keeps the Doppler width self-consistent with the actual position of each equivalent
+line.
+
+The contribution to the :ref:`propagation matrix ` from the entire band is
+then
+
+.. math::
+
+ K_{A, ecs} = N \nu \left(1 - \exp\!\left(-\frac{h\nu}{kT}\right)\right) \mathrm{Re}\!\left[F_{ECS}(\nu)\right],
+
+where :math:`N` is the total number density of the absorbing species.
+
+.. note::
+
+ Zeeman splitting within a band is not currently supported together with ECS
+ line mixing.
+
+.. _lbl-ecs-relaxmat:
+
+Relaxation Matrix
+=================
+
+The complex relaxation matrix :math:`\mathbf{W}` has dimensions
+:math:`n \times n` (lines in the band) and is constructed as follows.
+
+The *real* part of the diagonal elements carries the (pressure-shifted) line
+centre frequencies:
+
+.. math::
+
+ \mathrm{Re}\, W_{ii} = \nu_{0,i} + \Delta\nu_{P,0,i},
+
+where :math:`\nu_{0,i}` is the vacuum line centre and :math:`\Delta\nu_{P,0,i}` is the
+pressure shift of line :math:`i`.
+
+The *imaginary* part of the diagonal elements carries the pressure broadening:
+
+.. math::
+
+ \mathrm{Im}\, W_{ii} = G_{P,0,i},
+
+where :math:`G_{P,0,i}` is the pressure-broadening half-width half-maximum.
+
+The off-diagonal elements :math:`W_{ij}` (:math:`i \neq j`) encode the rate of
+transfer from line :math:`j` to line :math:`i` via collisions. Their construction
+from the ECS basis rates is described below.
+
+.. _lbl-ecs-rates:
+
+ECS Basis Rates
+===============
+
+The ECS approach introduces two species-dependent functions of the integer angular
+momentum transfer channel :math:`L`:
+
+**Basic rate** :math:`Q(L)`:
+ This encodes the intrinsic probability of a collision transferring :math:`L` units of
+ angular momentum. Its temperature dependence is parameterised as
+
+ .. math::
+
+ Q(L, T) = s(T) \cdot \frac{e^{-\beta(T)\, E_L / kT}}{[L(L+1)]^{\lambda(T)}},
+
+ where :math:`E_L` is the rotational energy of level :math:`L`,
+ and :math:`s(T)`, :math:`\beta(T)`, and :math:`\lambda(T)` are
+ temperature-dependent model parameters stored per broadening species
+ (see :ref:`lbl-line-shape-params` for the available temperature dependence forms).
+
+**Adiabatic factor** :math:`\Omega(L)`:
+ The IOS approximation becomes inaccurate when the rotational period approaches
+ the collision duration. The adiabatic factor corrects for this using the
+ coupling model:
+
+ .. math::
+
+ \Omega(L) = \frac{1}{\left[1 + \dfrac{\omega_{L,L-2}^2\, \tau_c^2}{24}\right]^2},
+
+ where :math:`\omega_{L,L-2} = (E_L - E_{L-2})/\hbar` is the angular frequency of the
+ :math:`L \to L-2` rotational transition and
+
+ .. math::
+
+ \tau_c = \frac{\sigma_c(T)}{\bar{v}}, \qquad
+ \bar{v} = \sqrt{\frac{8kT}{\pi\mu}},
+
+ with :math:`\sigma_c(T)` the temperature-dependent mean collisional diameter (a
+ per-species model parameter, distinct from the reduced dipole :math:`d_j`),
+ :math:`\mu` the reduced mass of the colliding pair, and :math:`\bar{v}` the
+ mean relative thermal speed.
+
+.. _lbl-ecs-offdiag:
+
+Off-diagonal Elements
+=====================
+
+The off-diagonal elements of :math:`\mathbf{W}` are computed species-by-species.
+All variants follow the formal IOS structure: they are written as a sum over
+even angular momentum transfer channels :math:`L`, weighted by the ratio
+:math:`Q(L)/\Omega(L)` and by Wigner 3-j and 6-j coupling coefficients.
+After the IOS computation an error-correction (sum-rule rescaling) is applied
+(see :ref:`lbl-ecs-sumrule`).
+
+Detailed balance is enforced throughout: the rate of transfer from a lower strength
+line :math:`j` to a higher strength line :math:`i` is obtained from the downward
+rate :math:`W_{ij}` via
+
+.. math::
+
+ W_{ji} = W_{ij} \exp\!\left(\frac{E_j - E_i}{kT}\right),
+
+where :math:`E_i` is the energy of the lower rotational state of line :math:`i`
+(using the same :math:`E_l` convention as in :ref:`lbl-lte`).
+
+The four variants implemented in ARTS, corresponding to the four line shape model
+types ``VP_ECS_HARTMANN``, ``VP_ECS_MAKAROV``, ``VP_ECS_STOTOP``, and ``VP_ECS_SPHTOP``,
+are described below.
+
+Linear Molecules — Hartmann (CO\ :sub:`2`)
+------------------------------------------
+
+For linear molecules (e.g. CO\ :sub:`2`) the off-diagonal rate from line :math:`j`
+(upper/lower rotational quantum numbers :math:`J'_i, J'_f`, vibrational angular
+momentum :math:`l`) to line :math:`i` (:math:`J_i, J_f`) is
+:cite:p:`NIRO2004483`
+
+.. math::
+
+ W_{ij} =
+ \Omega(J_i)\, (2J'_i+1)\sqrt{(2J_f+1)(2J'_f+1)}
+ \sum_L (2L+1)
+ \begin{pmatrix} J_i & J'_i & L \\ l & -l & 0 \end{pmatrix}
+ \begin{pmatrix} J_f & J'_f & L \\ l & -l & 0 \end{pmatrix}
+ \begin{Bmatrix} J_i & J_f & 1 \\ J'_f & J'_i & L \end{Bmatrix}
+ \frac{Q(L)}{\Omega(L)},
+
+where the sum runs over even :math:`L \geq \max(|J_i-J'_i|, |J_f-J'_f|)`,
+:math:`(\,\cdots)` denotes a Wigner 3-j symbol,
+and :math:`\{\,\cdots\}` a Wigner 6-j symbol.
+
+The corresponding reduced dipole element used in the equivalent-strength
+calculation is
+
+.. math::
+
+ d(J_f, J_i) = (-1)^{J_f + l_f + 1} \sqrt{2J_f+1}\;
+ \begin{pmatrix} J_f & 1 & J_i \\ l_i & l_f - l_i & -l_f \end{pmatrix}.
+
+The rotational energy entering :math:`Q` and :math:`\Omega` is the rigid-rotor
+expression :math:`E_J = B_0 J(J+1)`, where :math:`B_0` is the effective
+ground-state rotational constant for the species. Energy levels provided by
+quantum-chemical calculations are used directly where available; the rigid-rotor
+expression serves to extrapolate to levels not covered by those calculations.
+
+Symmetric Tops with Electron Spin — Makarov (O\ :sub:`2`)
+----------------------------------------------------------
+
+Molecular oxygen (O\ :sub:`2`) has an unpaired electron spin :math:`S = 1`, so each
+rotational quantum number :math:`N` gives rise to a triplet :math:`J = N-1, N, N+1`.
+The off-diagonal coupling between lines :math:`(i: N_l J_l \to N_u J_u)` and
+:math:`(j: N'_l J'_l \to N'_u J'_u)` is (using :math:`l`/:math:`u` for the
+lower/upper state of each transition, matching the convention of :ref:`lbl-lte`)
+:cite:p:`Makarov2020`
+
+.. math::
+
+ W_{ij} =&
+ (-1)^{J'_l + J_l + 1}\,
+ [N_l][N_u][N'_u][N'_l][J_u][J'_u][J_l][J'_l] \Omega(N_l) \\ &
+ \begin{array}{llll}
+ \sum_L (2L+1) &
+ \begin{pmatrix} N'_l & N_l & L \\ 0 & 0 & 0 \end{pmatrix} &
+ \begin{pmatrix} N'_u & N_u & L \\ 0 & 0 & 0 \end{pmatrix} \\ &
+ \begin{Bmatrix} L & J_l & J'_l \\ S & N'_l & N_l \end{Bmatrix} &
+ \begin{Bmatrix} L & J_u & J'_u \\ S & N'_u & N_u \end{Bmatrix} &
+ \begin{Bmatrix} L & J_l & J'_l \\ 1 & J'_u & J_u \end{Bmatrix}
+ \frac{Q(L)}{\Omega(L)},
+ \end{array}
+
+where :math:`[X] \equiv \sqrt{2X+1}`, :math:`(\cdots)` denotes a Wigner 3-j symbol,
+and :math:`\{\cdots\}` a Wigner 6-j symbol.
+
+The reduced dipole is
+
+.. math::
+
+ d(J_u, J_l, N) = (-1)^{J_l + N}
+ \sqrt{6(2J_l+1)(2J_u+1)}
+ \begin{Bmatrix} 1 & 1 & 1 \\ J_l & J_u & N \end{Bmatrix}.
+
+The rotational energy for the O\ :sub:`2` microwave band is computed from the
+full ground-state Hamiltonian including spin–rotation coupling and magnetic
+interactions.
+
+Symmetric Tops (NH\ :sub:`3`, PH\ :sub:`3`)
+-------------------------------------------
+
+This part is mostly untested and may be incorrect.
+It has been generated by AI and is available in ARTS
+only for experimentation to see if it produces reasonable results.
+
+For symmetric top molecules (e.g. NH\ :sub:`3`, PH\ :sub:`3`) with :math:`\Delta K = 0`
+collisions, lines within the same :math:`K` sub-band are coupled identically to the
+Hartmann linear-molecule formula with the vibrational angular momentum :math:`l`
+replaced by :math:`K`: :cite:p:`Hadded2002`
+
+.. math::
+
+ W_{ij} =
+ \Omega(J_i)\, (2J'_i+1)\sqrt{(2J_f+1)(2J'_f+1)}
+ \sum_L (2L+1)
+ \begin{pmatrix} J_i & J'_i & L \\ K & -K & 0 \end{pmatrix}
+ \begin{pmatrix} J_f & J'_f & L \\ K & -K & 0 \end{pmatrix}
+ \begin{Bmatrix} J_i & J_f & 1 \\ J'_f & J'_i & L \end{Bmatrix}
+ \frac{Q(L)}{\Omega(L)}.
+
+Lines with different :math:`K` are not coupled. The reduced dipole is
+
+.. math::
+
+ d(J_f, J_i, K) = (-1)^{J_f + K + 1}\sqrt{2J_f+1}\;
+ \begin{pmatrix} J_f & 1 & J_i \\ K & 0 & -K \end{pmatrix}.
+
+Rotational energy levels provided by quantum-chemical calculations are used
+directly where available; levels beyond those are extrapolated using the
+rigid-rotor expression :math:`E_J = B_0 J(J+1)` with the species-specific
+ground-state rotational constant :math:`B_0`.
+
+Spherical Tops (CH\ :sub:`4`)
+-----------------------------
+
+This part is mostly untested and may be incorrect.
+It has been generated by AI and is available in ARTS
+only for experimentation to see if it produces reasonable results.
+
+For spherical top molecules (e.g. CH\ :sub:`4`) the coupling reduces to the
+:math:`l = 0` limit of the Hartmann formula: :cite:p:`Pieroni1999`
+
+.. math::
+
+ W_{ij} =
+ \Omega(J_i)\, (2J'_i+1)\sqrt{(2J_f+1)(2J'_f+1)}
+ \sum_L (2L+1)
+ \begin{pmatrix} J_i & J'_i & L \\ 0 & 0 & 0 \end{pmatrix}
+ \begin{pmatrix} J_f & J'_f & L \\ 0 & 0 & 0 \end{pmatrix}
+ \begin{Bmatrix} J_i & J_f & 1 \\ J'_f & J'_i & L \end{Bmatrix}
+ \frac{Q(L)}{\Omega(L)},
+
+and the reduced dipole is
+
+.. math::
+
+ d(J_f, J_i) = (-1)^{J_f+1}\sqrt{2J_f+1}\;
+ \begin{pmatrix} J_f & 1 & J_i \\ 0 & 0 & 0 \end{pmatrix}.
+
+Rotational energy levels provided by quantum-chemical calculations are used
+directly where available; levels beyond those are extrapolated using the
+rigid-rotor expression :math:`E_J = B_0 J(J+1)` with the species-specific
+ground-state rotational constant :math:`B_0`.
+
+.. _lbl-ecs-sumrule:
+
+Sum-rule Correction
+===================
+
+The pure IOS matrix elements computed above do not, in general, satisfy the
+first-order optical sum rule exactly due to the finite range of the :math:`L` sum and
+the approximate nature of the adiabatic factor. The *error-correction* step
+rescales each column of off-diagonal elements to enforce
+
+.. math::
+
+ \sum_j d_j\, W_{ji} = 0 \quad \forall\, i.
+
+This is done as follows. For each line :math:`i`, partition the off-diagonal
+elements into those coupling to lines with lower intensity-weighted frequency
+(summed into :math:`s_\downarrow`) and those coupling to higher-frequency lines
+(:math:`s_\uparrow`):
+
+.. math::
+
+ s_\downarrow = \sum_{j > i} d_j\, W_{ji}, \qquad
+ s_\uparrow = \sum_{j < i} d_j\, W_{ji}.
+
+All downward-coupling elements are then rescaled by :math:`-s_\uparrow / s_\downarrow`,
+and the corresponding upward-coupling elements are updated by detailed balance.
+
+This rescaling constitutes the "error-corrected" part of the ECS method and ensures
+the resulting relaxation matrix produces physically consistent absorption profiles
+that recover the correct integrated line intensity at all pressures.
+
+.. _lbl-ecs-multispecies:
+
+Multiple Broadening Species
+===========================
+
+When multiple broadening species are present, the ARTS implementation offers two
+modes:
+
+In the **single-W mode** (used by default when calling ``calculate``), the per-species
+relaxation matrices are first volume-mixing-ratio weighted and summed into a single
+effective :math:`\mathbf{W}`:
+
+.. math::
+
+ \mathbf{W}_{eff} = \sum_s x_s\, \mathbf{W}^{(s)},
+
+and a single diagonalisation is performed.
+
+In the **multi-W mode** (used by ``equivalent_values`` for pre-computing equivalent
+lines at multiple temperatures), the diagonalisation is performed separately per
+species and the resulting absorption contributions are VMR-weighted and summed.
+
+.. _lbl-ecs-rosenkranz:
+
+Rosenkranz Approximation
+========================
+
+The full ECS calculation requires the diagonalisation of an :math:`n \times n`
+complex matrix at every temperature and pressure of interest, together with a
+VMR-weighted sum over broadening species. For many practical applications a
+simpler representation is desirable: the *Rosenkranz approximation* retains the
+ordinary Voigt line shape of each line but adds pressure-dependent first- and
+second-order correction terms that encode the effect of line mixing to a given
+order in pressure.
+
+The corrected Voigt line shape for line :math:`i` is exactly the :ref:`Voigt
+profile ` already described,
+
+.. math::
+
+ F_i = \frac{1 + G_{lm,i} - iY_{lm,i}}{\sqrt{\pi}\,G_D}\,w(z_i),
+
+where :math:`z_i` contains :math:`\Delta\nu_{lm,i}` as an additional shift, and
+the three correction parameters are:
+
+.. list-table::
+ :header-rows: 1
+
+ * - Parameter
+ - Physical meaning
+ - Pressure scaling
+ * - :math:`Y_{lm,i}`
+ - First-order line-mixing: asymmetric intensity redistribution between nearby lines.
+ - :math:`P`
+ * - :math:`G_{lm,i}`
+ - Second-order strength correction: quadratic-in-pressure modification to the
+ integrated area.
+ - :math:`P^2`
+ * - :math:`\Delta\nu_{lm,i}`
+ - Second-order frequency shift: quadratic-in-pressure displacement of the line
+ centre due to the mixing.
+ - :math:`P^2`
+
+The Rosenkranz parameters are not fitted to measured spectra directly; instead they
+are derived from the ECS equivalent lines or, equivalently, from the relaxation matrix
+itself via perturbation theory — both approaches are described below.
+
+.. _lbl-ecs-rosenkranz-W:
+
+Perturbation Theory from the Relaxation Matrix
+-----------------------------------------------
+
+When the off-diagonal elements of :math:`\mathbf{W}` are small compared with the
+spacings between line centres (the "weak coupling" limit, valid for resolved lines or
+moderate pressures), the Rosenkranz parameters can be obtained analytically by
+expanding the resolvent :math:`(\nu\mathbf{I} - \mathbf{W})^{-1}` in powers of the
+off-diagonal part. This is the original approach of :cite:t:`rosenkranz:75`.
+
+Write :math:`\mathbf{W} = \mathbf{D} + \mathbf{V}`, where :math:`\mathbf{D}` is the
+diagonal part (line centres plus pressure broadening) and :math:`\mathbf{V}_{ij} =
+W_{ij}` for :math:`i \neq j` (the off-diagonal relaxation rates, purely imaginary in
+the ARTS convention: :math:`V_{ij} = i R_{ij}` with :math:`R_{ij}` real and
+proportional to :math:`P`). Let :math:`g_i(\nu) = [\nu - W_{ii}]^{-1}` be the
+unperturbed resolvent for line :math:`i`. The Neumann expansion then gives
+
+.. math::
+
+ (\nu\mathbf{I} - \mathbf{W})^{-1} =
+ \mathbf{G}_0 + \mathbf{G}_0 \mathbf{V} \mathbf{G}_0
+ + \mathbf{G}_0 \mathbf{V} \mathbf{G}_0 \mathbf{V} \mathbf{G}_0 + \cdots,
+
+where :math:`\mathbf{G}_0 = \mathrm{diag}(g_i(\nu))`.
+
+Collecting all contributions to the absorption of line :math:`i` through first and
+second order, and evaluating the slowly varying factors involving other lines :math:`j`
+at :math:`\nu = \nu_i`, yields the three Rosenkranz parameters for line :math:`i`:
+
+**First-order mixing parameter** (:math:`Y_i \sim P`):
+
+.. math::
+
+ Y_i = \frac{2}{S_i} \sum_{j \neq i} S_j \frac{R_{ij}}{\nu_i - \nu_j},
+
+where :math:`S_i = p_i d_i^2` is proportional to the LBL line strength of line
+:math:`i` (see :ref:`lbl-ecs-lineshape` for the definition of :math:`p_i`),
+and
+:math:`R_{ij} = \mathrm{Im}[W_{ij}] / P` is the pressure-normalised off-diagonal
+relaxation rate (transfer from line :math:`j` to line :math:`i`; note
+:math:`\mathrm{Re}[W_{ij}] = 0` for :math:`i \neq j`), and the sum is over
+all other lines :math:`j` in the band.
+
+**Second-order strength correction** (:math:`G_i \sim P^2`):
+
+From the squared first-order cross terms, the fractional modification to the
+integrated area of line :math:`i` is
+
+.. math::
+
+ G_i = -\frac{1}{S_i} \sum_{j \neq i} S_j \left(\frac{R_{ij}}{\nu_i - \nu_j}\right)^2.
+
+**Second-order line-centre shift** (:math:`\Delta\nu_i \sim P^2`):
+
+The diagonal self-energy correction (virtual transition :math:`i \to j \to i`) gives
+
+.. math::
+
+ \Delta\nu_i = -\frac{1}{S_i} \sum_{j \neq i} S_j \frac{R_{ij}^2}{\nu_i - \nu_j},
+
+where detailed balance (:math:`S_i R_{ji} = S_j R_{ij}`) has been used to express
+everything in terms of the downward rate :math:`R_{ij}`.
+
+.. note::
+
+ Note that :math:`\Delta\nu_i = G_i (\nu_i - \nu_j)` only for a single interfering
+ line. In general they have different frequency denominators (:math:`\nu_i - \nu_j`
+ vs :math:`(\nu_i - \nu_j)^2`) and thus differ quantitatively when multiple lines
+ contribute. The two parameters are both needed to correctly reproduce the
+ second-order pressure dependence of the band profile.
+
+ The perturbation theory expressions above assume the off-diagonal elements are
+ small relative to the line spacing. They break down for overlapping lines
+ (e.g., at very high pressures or for lines very close in frequency). In that
+ regime the full ECS calculation should be used instead.
+
+.. _lbl-ecs-rosenkranz-fitting:
+
+Fitting from Equivalent Lines
+------------------------------
+
+The perturbation theory expressions above are analytically exact in the weak-coupling
+limit, but in practice it is often more accurate to extract the Rosenkranz parameters
+*numerically* from the ECS equivalent lines, because the equivalent-line calculation
+already includes the full resummation of the relaxation matrix (not just the first few
+terms of the Neumann series). The two approaches agree at low pressure but the
+equivalent-line fit is preferred at higher pressures where the perturbation series
+converges slowly.
+
+Given the complex equivalent lines :math:`(\tilde{S}_{k,s}, \tilde{\nu}_{k,s})`
+(indexed by :math:`k` in eigenvalue-decomposition order, which carries no physical
+meaning) computed by ECS for broadening species :math:`s` at pressure :math:`P_0`
+and a grid of temperatures :math:`T_1, \ldots, T_M`, the Rosenkranz coefficients are
+extracted by ``abs_bandsLineMixingAdaptation`` as follows.
+Here, :math:`i` will denote the index of the physical LBL lines (the rows/columns of
+:math:`\mathbf{W}`) and :math:`k` the index of the equivalent lines.
+
+**Step 1 — Sort and match.**
+The :math:`n` equivalent lines are sorted by :math:`\mathrm{Re}[\tilde{\nu}_{k,s}]`
+and the :math:`n` physical LBL lines are sorted by :math:`\nu_{0,i}`. Equivalent line
+at sorted position :math:`n` is then identified with the physical line at sorted
+position :math:`n`, giving a bijection :math:`k \leftrightarrow i` between the two
+index sets. This is a heuristic matching: because eigenvalue decomposition does not
+guarantee any particular ordering of eigenvalues, sorting is the only way to establish a
+correspondence. The identification is reliable as long as the second-order frequency
+shifts and pressure shifts remain small compared with the separations between adjacent
+line centres; it can fail if either effect is comparable in magnitude to the line spacing.
+
+**Step 2 — Form normalised differences.**
+For each matched pair :math:`(k, i)`, the LTE line strength of physical line :math:`i`
+at temperature :math:`T` is :math:`S_{LTE,i}(T)` as defined in :ref:`lbl-lte`
+(without the number density factor :math:`\rho`; the equivalent per-molecule strength is
+:math:`s_i(T) = S_{LTE,i}(T)/\rho`). The unperturbed complex frequency of physical
+line :math:`i` is
+
+.. math::
+
+ \nu_i^{LBL}(T) = \nu_{0,i} + \Delta\nu_{P,0,i}(T,P_0) + i\,G_{P,0,i}(T,P_0).
+
+The residual strength ratio and frequency residual are then formed:
+
+.. math::
+
+ r_{i,s}(T) &= \frac{\tilde{S}_{k,s}(T)}{S_{LTE,i}(T)}, \\
+ \delta\nu_{i,s}(T) &= \tilde{\nu}_{k,s}(T) - \nu_i^{LBL}(T),
+
+where :math:`k` is the equivalent line matched to physical line :math:`i` in Step 1.
+
+**Step 3 — Extract pressure-normalised Rosenkranz coefficients.**
+The three coefficients for physical line :math:`i` at the reference pressure
+:math:`P_0` are read off as:
+
+.. math::
+
+ Y_{lm,i,s}(T) &= \frac{\mathrm{Im}\!\left[r_{i,s}(T)\right]}{P_0}, \\[4pt]
+ G_{lm,i,s}(T) &= \frac{\mathrm{Re}\!\left[r_{i,s}(T)\right] - 1}{P_0^2}, \\[4pt]
+ \Delta\nu_{lm,i,s}(T) &= \frac{\mathrm{Re}\!\left[\delta\nu_{i,s}(T)\right]}{P_0^2}.
+
+The imaginary part of :math:`\delta\nu_{i,s}(T)` — which represents the
+correction to the pressure-broadening half-width — is divided by
+:math:`P_0^3` but is not retained as a separate Rosenkranz coefficient
+(it is already captured by the diagonal of :math:`\mathbf{W}` at first
+order in :math:`P`).
+
+**Step 4 — Polynomial fit in temperature.**
+Each of the three coefficients is fitted as a polynomial in temperature of
+configurable degree :math:`d`:
+
+.. math::
+
+ Y_{lm,i,s}(T) &\approx \sum_{n=0}^{d} a_n^{(Y)}\,T^n, \\
+ G_{lm,i,s}(T) &\approx \sum_{n=0}^{d} a_n^{(G)}\,T^n, \\
+ \Delta\nu_{lm,i,s}(T) &\approx \sum_{n=0}^{d} a_n^{(DV)}\,T^n.
+
+These polynomials are stored using the ``POLY`` temperature model (see
+:ref:`lbl-line-shape-params`) for each broadening species separately and
+are then evaluated at runtime using the ordinary VMR-weighted sum of the
+:ref:`line shape parameter ` framework, with the
+coefficients attached to physical line :math:`i`.
+
+.. note::
+
+ Setting ``rosenkranz_fit_order = 1`` retains only :math:`Y_{lm}` and is
+ appropriate for moderate pressures where the quadratic-in-pressure corrections
+ are negligible. Setting it to 2 also fits :math:`G_{lm}` and
+ :math:`\Delta\nu_{lm}`, which is necessary at higher pressures or when
+ second-order effects are important (e.g. near the Q-branch of O :sub:`2`
+ at tens of GHz).
+
+ The polynomial fits implicitly assume that the reference pressure :math:`P_0`
+ is fixed; the resulting coefficients must be used at the same pressure
+ normalisation. This is handled automatically when the output of
+ ``abs_bandsLineMixingAdaptation`` is fed back into the ordinary line-by-line
+ calculation.
+
diff --git a/doc/arts/references.bib b/doc/arts/references.bib
index c0baece56d..2f245934cd 100644
--- a/doc/arts/references.bib
+++ b/doc/arts/references.bib
@@ -2326,3 +2326,50 @@ @article{Ellison2007
url = {https://doi.org/10.1063/1.2360986},
eprint = {https://pubs.aip.org/aip/jpr/article-pdf/36/1/1/14719718/1_1_online.pdf},
}
+
+@article{NIRO2004483,
+title = {Spectra calculations in central and wing regions of CO2 IR bands between 10 and 20μm. I: model and laboratory measurements},
+journal = {Journal of Quantitative Spectroscopy and Radiative Transfer},
+volume = {88},
+number = {4},
+pages = {483-498},
+year = {2004},
+issn = {0022-4073},
+doi = {https://doi.org/10.1016/j.jqsrt.2004.04.003},
+url = {https://www.sciencedirect.com/science/article/pii/S0022407304001049},
+author = {F Niro and C Boulet and J.-M Hartmann},
+keywords = {CO, Infrared, Absorption, Shape, Model, Laboratory, Spectra},
+}
+
+@article{Pieroni1999,
+ author = {Pieroni, D. and Nguyen-Van-Thanh and Brodbeck, C. and
+ Claveau, C. and Valentin, A. and Hartmann, J. M. and Gabard, T. and
+ Champion, J.-P. and Bermejo, D. and Domenech, J.-L.},
+ title = {Experimental and theoretical study of line mixing in methane spectra. I.
+ The N$_2$-broadened $\nu_3$ band at room temperature},
+ journal = {The Journal of Chemical Physics},
+ volume = {110},
+ number = {16},
+ pages = {7717-7732},
+ year = {1999},
+ month = {04},
+ issn = {0021-9606},
+ doi = {10.1063/1.478724},
+ url = {https://doi.org/10.1063/1.478724},
+ eprint = {https://pubs.aip.org/aip/jcp/article-pdf/110/16/7717/19328457/7717_1_online.pdf},
+}
+
+@article{Hadded2002,
+ author = {Hadded, S. and Thibault, F. and Flaud, P.-M. and Aroui, H. and Hartmann, J.-M.},
+ title = {Experimental and theoretical study of line mixing in NH$_3$ spectra. I. Scaling analysis of parallel bands perturbed by He},
+ journal = {The Journal of Chemical Physics},
+ volume = {116},
+ number = {17},
+ pages = {7544-7557},
+ year = {2002},
+ month = {05},
+ issn = {0021-9606},
+ doi = {10.1063/1.1463442},
+ url = {https://doi.org/10.1063/1.1463442},
+ eprint = {https://pubs.aip.org/aip/jcp/article-pdf/116/17/7544/19021600/7544_1_online.pdf},
+}
diff --git a/src/core/lbl/CMakeLists.txt b/src/core/lbl/CMakeLists.txt
index a11a3d6696..2e6c0560eb 100644
--- a/src/core/lbl/CMakeLists.txt
+++ b/src/core/lbl/CMakeLists.txt
@@ -8,6 +8,8 @@ add_library(lbl STATIC
lbl_lineshape_voigt_ecs.cpp
lbl_lineshape_voigt_ecs_hartmann.cpp
lbl_lineshape_voigt_ecs_makarov.cpp
+ lbl_lineshape_voigt_ecs_stotop.cpp
+ lbl_lineshape_voigt_ecs_sphtop.cpp
lbl_lineshape_voigt_lte.cpp
lbl_lineshape_voigt_lte_mirrored.cpp
lbl_lineshape_voigt_lte_matrix.cpp
diff --git a/src/core/lbl/lbl_lineshape.cpp b/src/core/lbl/lbl_lineshape.cpp
index 2eba837b22..11f4f1fd6b 100644
--- a/src/core/lbl/lbl_lineshape.cpp
+++ b/src/core/lbl/lbl_lineshape.cpp
@@ -62,7 +62,9 @@ std::unique_ptr init_voigt_abs_ecs_data(
const Vector2 los) {
if (stdr::any_of(bnds | stdv::values, [](auto& bnd) {
return bnd.lineshape == LineByLineLineshape::VP_ECS_MAKAROV or
- bnd.lineshape == LineByLineLineshape::VP_ECS_HARTMANN;
+ bnd.lineshape == LineByLineLineshape::VP_ECS_HARTMANN or
+ bnd.lineshape == LineByLineLineshape::VP_ECS_STOTOP or
+ bnd.lineshape == LineByLineLineshape::VP_ECS_SPHTOP;
}))
return std::make_unique(
f_grid, atm, los, ZeemanPolarization::no);
@@ -177,7 +179,9 @@ void calculate(PropmatVectorView pm,
calc_voigt_line_nlte(bnd_key, bnd, pol);
break;
case LineByLineLineshape::VP_ECS_MAKAROV: [[fallthrough]];
- case LineByLineLineshape::VP_ECS_HARTMANN:
+ case LineByLineLineshape::VP_ECS_HARTMANN: [[fallthrough]];
+ case LineByLineLineshape::VP_ECS_STOTOP: [[fallthrough]];
+ case LineByLineLineshape::VP_ECS_SPHTOP:
calc_voigt_ecs_linemixing(bnd_key, bnd, pol);
break;
}
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs.cpp b/src/core/lbl/lbl_lineshape_voigt_ecs.cpp
index 5763cb04bd..5ca077cbb9 100644
--- a/src/core/lbl/lbl_lineshape_voigt_ecs.cpp
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs.cpp
@@ -21,6 +21,8 @@
#include "lbl_lineshape_model.h"
#include "lbl_lineshape_voigt_ecs_hartmann.h"
#include "lbl_lineshape_voigt_ecs_makarov.h"
+#include "lbl_lineshape_voigt_ecs_sphtop.h"
+#include "lbl_lineshape_voigt_ecs_stotop.h"
#undef WIGNER3
#undef WIGNER6
@@ -187,6 +189,15 @@ void ComputeData::adapt_multi(const QuantumIdentifier& bnd_qid,
auto& l2 = bnd_qid.state.at(QuantumNumberType::l2);
dipr[i] = hartmann::reduced_dipole(J.upper, J.lower, l2.upper, l2.lower);
dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_STOTOP) {
+ auto& J = bnd.lines[i].qn.at(QuantumNumberType::J);
+ auto& Kq = bnd.lines[i].qn.at(QuantumNumberType::K);
+ dipr[i] = stotop::reduced_dipole(J.upper, J.lower, Kq.lower);
+ dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_SPHTOP) {
+ auto& J = bnd.lines[i].qn.at(QuantumNumberType::J);
+ dipr[i] = sphtop::reduced_dipole(J.upper, J.lower);
+ dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
}
}
@@ -254,6 +265,12 @@ void ComputeData::adapt_multi(const QuantumIdentifier& bnd_qid,
} else if (bnd.lineshape == LineByLineLineshape::VP_ECS_HARTMANN) {
hartmann::relaxation_matrix_offdiagonal(
Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_STOTOP) {
+ stotop::relaxation_matrix_offdiagonal(
+ Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_SPHTOP) {
+ sphtop::relaxation_matrix_offdiagonal(
+ Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
} else {
ARTS_USER_ERROR("UNKNOWN ECS LINE SHAPE {}", bnd.lineshape)
}
@@ -314,6 +331,15 @@ void ComputeData::adapt_single(const QuantumIdentifier& bnd_qid,
auto& l2 = bnd_qid.state.at(QuantumNumberType::l2);
dipr[i] = hartmann::reduced_dipole(J.upper, J.lower, l2.upper, l2.lower);
dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_STOTOP) {
+ auto& J = bnd.lines[i].qn.at(QuantumNumberType::J);
+ auto& Kq = bnd.lines[i].qn.at(QuantumNumberType::K);
+ dipr[i] = stotop::reduced_dipole(J.upper, J.lower, Kq.lower);
+ dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_SPHTOP) {
+ auto& J = bnd.lines[i].qn.at(QuantumNumberType::J);
+ dipr[i] = sphtop::reduced_dipole(J.upper, J.lower);
+ dip[i] *= std::signbit(dipr[i]) ? -1 : 1;
}
}
@@ -382,6 +408,12 @@ void ComputeData::adapt_single(const QuantumIdentifier& bnd_qid,
} else if (bnd.lineshape == LineByLineLineshape::VP_ECS_HARTMANN) {
hartmann::relaxation_matrix_offdiagonal(
Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_STOTOP) {
+ stotop::relaxation_matrix_offdiagonal(
+ Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
+ } else if (bnd.lineshape == LineByLineLineshape::VP_ECS_SPHTOP) {
+ sphtop::relaxation_matrix_offdiagonal(
+ Wimag, bnd_qid, bnd, sort, spec, rovib_data_it->second, dipr, atm);
} else {
ARTS_USER_ERROR("UNKNOWN ECS LINE SHAPE {}", bnd.lineshape)
}
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs_hartmann.cpp b/src/core/lbl/lbl_lineshape_voigt_ecs_hartmann.cpp
index 6bec022604..7daa2040c7 100644
--- a/src/core/lbl/lbl_lineshape_voigt_ecs_hartmann.cpp
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs_hartmann.cpp
@@ -35,7 +35,7 @@ Numeric wig6(const Rational& a,
}
std::function erot_selection(const SpeciesIsotope& isot) {
- if (isot.spec == SpeciesEnum::CarbonDioxide and isot.isotname == "626") {
+ if (isot == "CO2-626"_isot) {
return [](const Rational J) -> Numeric {
return Conversion::kaycm2joule(0.39021) * Numeric(J * (J + 1));
};
@@ -79,7 +79,6 @@ void relaxation_matrix_offdiagonal(MatrixView& W,
using std::swap;
const bool swap_order = li > lf;
if (swap_order) swap(li, lf);
- const int sgn = iseven(li + lf + 1) ? -1 : 1;
if (abs(li - lf) > 1) return;
const Numeric T = atm.temperature;
@@ -129,20 +128,20 @@ void relaxation_matrix_offdiagonal(MatrixView& W,
if (Jf_p > Jf) continue;
Index L = std::max(std::abs((Ji - Ji_p).toIndex()),
- std::abs((Jf - Jf_p).toIndex()));
+ std::abs((Jf - Jf_p).toIndex()));
L += L % 2;
const Index Lf = std::min((Ji + Ji_p).toIndex(), (Jf + Jf_p).toIndex());
Numeric sum = 0;
for (; L <= Lf; L += 2) {
- const Numeric a = wig3(Ji_p, Rational{L}, Ji, li, Rational{0}, -li);
- const Numeric b = wig3(Jf_p, Rational{L}, Jf, lf, Rational{0}, -lf);
+ const Numeric a = wig3(Ji, Ji_p, Rational{L}, li, -li, Rational{0});
+ const Numeric b = wig3(Jf, Jf_p, Rational{L}, lf, -lf, Rational{0});
const Numeric c = wig6(Ji, Jf, Rational{1}, Jf_p, Ji_p, Rational{L});
sum += a * b * c * Numeric(2 * L + 1) * Q[L] / Om[L];
}
const Numeric ECS = Om[Ji.toIndex()];
- const Numeric scl = sgn * ECS * Numeric(2 * Ji_p + 1) *
- sqrtr((2 * Jf + 1) * (2 * Jf_p + 1));
+ const Numeric scl =
+ ECS * Numeric(2 * Ji_p + 1) * sqrtr((2 * Jf + 1) * (2 * Jf_p + 1));
sum *= scl;
// Add to W and rescale to upwards element by the populations
@@ -154,11 +153,6 @@ void relaxation_matrix_offdiagonal(MatrixView& W,
ARTS_USER_ERROR_IF(errno == EDOM, "Cannot compute the wigner symbols")
- // Undocumented negative absolute sign
- for (Size i = 0; i < n; i++)
- for (Size j = 0; j < n; j++)
- if (j not_eq i and W[i, j] > 0) W[i, j] *= -1;
-
// Sum rule correction
for (Size i = 0; i < n; i++) {
Numeric sumlw = 0.0;
@@ -166,9 +160,9 @@ void relaxation_matrix_offdiagonal(MatrixView& W,
for (Size j = 0; j < n; j++) {
if (j > i) {
- sumlw += std::abs(dipr[j]) * W[j, i]; // Undocumented abs-sign
+ sumlw += dipr[j] * W[j, i];
} else {
- sumup += std::abs(dipr[j]) * W[j, i]; // Undocumented abs-sign
+ sumup += dipr[j] * W[j, i];
}
}
@@ -182,7 +176,7 @@ void relaxation_matrix_offdiagonal(MatrixView& W,
} else {
W[j, i] *= -sumup / sumlw;
W[i, j] = W[j, i] * std::exp((erot(Ji) - erot(Jj)) /
- kelvin2joule(T)); // This gives LTE
+ kelvin2joule(T)); // This gives LTE
}
}
}
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.cpp b/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.cpp
new file mode 100644
index 0000000000..13d7fc1978
--- /dev/null
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.cpp
@@ -0,0 +1,195 @@
+#include "lbl_lineshape_voigt_ecs_sphtop.h"
+
+#include
+#include
+#include
+
+namespace lbl::voigt::ecs::sphtop {
+#if DO_FAST_WIGNER
+#define WIGNER3 fw3jja6
+#define WIGNER6 fw6jja
+#else
+#define WIGNER3 wig3jj
+#define WIGNER6 wig6jj
+#endif
+
+namespace {
+Numeric wig3(const Rational& a,
+ const Rational& b,
+ const Rational& c,
+ const Rational& d,
+ const Rational& e,
+ const Rational& f) {
+ return WIGNER3(
+ a.toInt(2), b.toInt(2), c.toInt(2), d.toInt(2), e.toInt(2), f.toInt(2));
+}
+
+Numeric wig6(const Rational& a,
+ const Rational& b,
+ const Rational& c,
+ const Rational& d,
+ const Rational& e,
+ const Rational& f) {
+ return WIGNER6(
+ a.toInt(2), b.toInt(2), c.toInt(2), d.toInt(2), e.toInt(2), f.toInt(2));
+}
+
+/*! Compute rotational energy for a spherical top molecule
+ *
+ * E(J) = B*J*(J+1)
+ *
+ * Species-specific rotational constants:
+ * CH4-211 (12CH4): B0 = 5.2410 cm^{-1}
+ *
+ * @param[in] isot The isotopologue
+ * @return A function J -> E(J) in Joule
+ */
+std::function erot_selection(const SpeciesIsotope& isot) {
+ // CH4 main isotopologue (12C-1H4)
+ if (isot == "CH4-211"_isot) {
+ return [](const Rational J) -> Numeric {
+ return Conversion::kaycm2joule(5.2410) * Numeric(J * (J + 1));
+ };
+ }
+
+ ARTS_USER_ERROR("{} has no rotational energies for spherical top ECS in ARTS",
+ isot.FullName())
+ return [](const Rational J) -> Numeric {
+ return Numeric(J) * std::numeric_limits::signaling_NaN();
+ };
+}
+} // namespace
+
+Numeric reduced_dipole(const Rational Jf, const Rational Ji) {
+ // d(Jf, Ji) = (-1)^{Jf+1} sqrt(2*Jf+1) * 3j(Jf, 1, Ji; 0, 0, 0)
+ // This is the l=0 limit of the Hartmann formula.
+ if (not iseven(Jf + 1))
+ return -sqrtr(2 * Jf + 1) *
+ wigner3j(Jf, Rational{1}, Ji, Rational{0}, Rational{0}, Rational{0});
+ return +sqrtr(2 * Jf + 1) *
+ wigner3j(Jf, Rational{1}, Ji, Rational{0}, Rational{0}, Rational{0});
+}
+
+void relaxation_matrix_offdiagonal(MatrixView& W,
+ const QuantumIdentifier& bnd_qid,
+ const band_data& bnd,
+ const ArrayOfIndex& sorting,
+ const SpeciesEnum broadening_species,
+ const linemixing::species_data& rovib_data,
+ const Vector& dipr,
+ const AtmPoint& atm) {
+ using Conversion::kelvin2joule;
+
+ const Size n = bnd.size();
+ if (not n) return;
+
+ const Numeric T = atm.temperature;
+
+ const auto erot = erot_selection(bnd_qid.isot);
+
+ const std::array rats{bnd.max(QuantumNumberType::J)};
+ const int maxL = wigner_init_size(rats);
+
+ const auto Om = [&]() {
+ Vector out(maxL);
+ for (Index i = 0; i < maxL; i++)
+ out[i] = rovib_data.Omega(atm.temperature,
+ bnd.front().ls.T0,
+ broadening_species == SpeciesEnum::Bath
+ ? atm.mean_mass()
+ : atm.mean_mass(broadening_species),
+ bnd_qid.isot.mass,
+ erot(Rational{i}),
+ erot(Rational{i - 2}));
+ return out;
+ }();
+
+ const auto Q = [&]() {
+ Vector out(maxL);
+ for (Index i = 0; i < maxL; i++)
+ out[i] = rovib_data.Q(
+ Rational{i}, atm.temperature, bnd.front().ls.T0, erot(Rational{i}));
+ return out;
+ }();
+
+ // The coupling for spherical tops with l=0 is:
+ //
+ // R(J→J'; 0) = (-1)^{J+J'+1} (2J'+1) Ω(J)
+ // × Σ_L (2L+1) 3j(J, J', L; 0, 0, 0) × 3j(Jf, Jf', L; 0, 0, 0)
+ // × 6j(Ji, Jf, 1; Jf', Ji', L) × Q(L)/Ω(L)
+ //
+ // This is identical to the Hartmann (linear molecule) formula with
+ // l_i = l_f = 0.
+
+ arts_wigner_thread_init(maxL);
+ for (Size i = 0; i < n; i++) {
+ auto& J = bnd.lines[sorting[i]].qn.at(QuantumNumberType::J);
+ const Rational Ji = J.upper;
+ const Rational Jf = J.lower;
+
+ for (Size j = 0; j < n; j++) {
+ if (i == j) continue;
+
+ auto& J_p = bnd.lines[sorting[j]].qn.at(QuantumNumberType::J);
+ const Rational Ji_p = J_p.upper;
+ const Rational Jf_p = J_p.lower;
+
+ // Only compute the downward element (J' ≤ J)
+ if (Jf_p > Jf) continue;
+
+ Index L = std::max(std::abs((Ji - Ji_p).toIndex()),
+ std::abs((Jf - Jf_p).toIndex()));
+ L += L % 2;
+ const Index Lf = std::min((Ji + Ji_p).toIndex(), (Jf + Jf_p).toIndex());
+
+ Numeric sum = 0;
+ for (; L <= Lf; L += 2) {
+ const Numeric a =
+ wig3(Ji, Ji_p, Rational{L}, Rational{0}, Rational{0}, Rational{0});
+ const Numeric b =
+ wig3(Jf, Jf_p, Rational{L}, Rational{0}, Rational{0}, Rational{0});
+ const Numeric c = wig6(Ji, Jf, Rational{1}, Jf_p, Ji_p, Rational{L});
+ sum += a * b * c * Numeric(2 * L + 1) * Q[L] / Om[L];
+ }
+ const Numeric ECS = Om[Ji.toIndex()];
+ const Numeric scl =
+ ECS * Numeric(2 * Ji_p + 1) * sqrtr((2 * Jf + 1) * (2 * Jf_p + 1));
+ sum *= scl;
+
+ // Downward element and detailed balance for upward
+ W[j, i] = sum;
+ W[i, j] = sum * std::exp((erot(Jf_p) - erot(Jf)) / kelvin2joule(T));
+ }
+ }
+ arts_wigner_thread_free();
+
+ ARTS_USER_ERROR_IF(errno == EDOM, "Cannot compute the wigner symbols")
+
+ // Sum rule correction
+ for (Size i = 0; i < n; i++) {
+ Numeric sumlw = 0.0;
+ Numeric sumup = 0.0;
+
+ for (Size j = 0; j < n; j++) {
+ if (j > i) {
+ sumlw += dipr[j] * W[j, i];
+ } else {
+ sumup += dipr[j] * W[j, i];
+ }
+ }
+
+ const Rational Ji = bnd.lines[sorting[i]].qn.at(QuantumNumberType::J).lower;
+ for (Size j = i + 1; j < n; j++) {
+ const Rational Jj =
+ bnd.lines[sorting[j]].qn.at(QuantumNumberType::J).lower;
+ if (std::abs(sumlw) <= std::abs(sumup) * 1e-6) {
+ W[j, i] = 0.0;
+ W[i, j] = 0.0;
+ } else {
+ W[j, i] *= -sumup / sumlw;
+ W[i, j] = W[j, i] * std::exp((erot(Ji) - erot(Jj)) / kelvin2joule(T));
+ }
+ }
+ }
+}
+} // namespace lbl::voigt::ecs::sphtop
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.h b/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.h
new file mode 100644
index 0000000000..03350a979f
--- /dev/null
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs_sphtop.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include
+
+#include "lbl_data.h"
+#include "lbl_lineshape_linemixing.h"
+
+namespace lbl::voigt::ecs::sphtop {
+/*! Returns the reduced dipole for a spherical top molecule
+ *
+ * For a spherical top (Td symmetry), the sub-level structure (A1, A2, E,
+ * F1, F2) is already resolved at the band level in the ARTS catalog.
+ * Within each symmetry sub-band, the lines are characterized only by J
+ * (total angular momentum). The reduced dipole is the l=0 limit of the
+ * linear molecule formula:
+ *
+ * d(Jf, Ji) = (-1)^{Jf+1} sqrt(2*Jf+1) * 3j(Jf, 1, Ji; 0, 0, 0)
+ *
+ * This is equivalent to the Hartmann formula with l_i = l_f = 0.
+ *
+ * @param[in] Jf Lower state total angular momentum
+ * @param[in] Ji Upper state total angular momentum
+ * @return The reduced dipole
+ */
+Numeric reduced_dipole(const Rational Jf, const Rational Ji);
+
+/*! Compute the off-diagonal elements of the relaxation matrix
+ * for spherical top molecules (CH4, etc.)
+ *
+ * Uses the ECS-EP (Energy Corrected Sudden with Exponential Power law)
+ * formalism. For spherical tops, the tetrahedral sub-level structure
+ * is already separated into distinct ARTS bands (by rovibSym and alpha),
+ * so within a single band the coupling is identical to the linear molecule
+ * case with l=0. The 3j symbols simplify to 3j(J, J', L; 0, 0, 0).
+ *
+ * @param[in,out] W Relaxation matrix (diagonal already set)
+ * @param[in] bnd_qid Band quantum identifier
+ * @param[in] bnd Band data
+ * @param[in] sorting Index sorting of the lines
+ * @param[in] broadening_species Which broadening species to use
+ * @param[in] rovib_data ECS-EP parameters for the species
+ * @param[in] dipr Reduced dipole moments
+ * @param[in] atm Atmospheric point
+ */
+void relaxation_matrix_offdiagonal(MatrixView& W,
+ const QuantumIdentifier& bnd_qid,
+ const band_data& bnd,
+ const ArrayOfIndex& sorting,
+ const SpeciesEnum broadening_species,
+ const linemixing::species_data& rovib_data,
+ const Vector& dipr,
+ const AtmPoint& atm);
+} // namespace lbl::voigt::ecs::sphtop
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.cpp b/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.cpp
new file mode 100644
index 0000000000..a223250418
--- /dev/null
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.cpp
@@ -0,0 +1,230 @@
+#include "lbl_lineshape_voigt_ecs_stotop.h"
+
+#include
+#include
+#include
+
+namespace lbl::voigt::ecs::stotop {
+#if DO_FAST_WIGNER
+#define WIGNER3 fw3jja6
+#define WIGNER6 fw6jja
+#else
+#define WIGNER3 wig3jj
+#define WIGNER6 wig6jj
+#endif
+
+namespace {
+Numeric wig3(const Rational& a,
+ const Rational& b,
+ const Rational& c,
+ const Rational& d,
+ const Rational& e,
+ const Rational& f) {
+ return WIGNER3(
+ a.toInt(2), b.toInt(2), c.toInt(2), d.toInt(2), e.toInt(2), f.toInt(2));
+}
+
+Numeric wig6(const Rational& a,
+ const Rational& b,
+ const Rational& c,
+ const Rational& d,
+ const Rational& e,
+ const Rational& f) {
+ return WIGNER6(
+ a.toInt(2), b.toInt(2), c.toInt(2), d.toInt(2), e.toInt(2), f.toInt(2));
+}
+
+/*! Compute rotational energy for a symmetric top molecule
+ *
+ * E(J, K) = B*J*(J+1) + (A-B)*K^2 - D_J*[J*(J+1)]^2
+ * - D_JK*J*(J+1)*K^2 - D_K*K^4
+ *
+ * For the ECS basis rates we only need E(L) for the collisional
+ * angular momentum transfer channel, where L has no K-dependence
+ * (the basis rates are for the atom-like IOS limit). So we use
+ * the simple rigid rotor formula E = B*J*(J+1).
+ *
+ * Species-specific rotational constants:
+ * NH3-4111 (14NH3): B0 = 9.9402 cm^{-1}
+ * PH3-1111 (31PH3): B0 = 4.4522 cm^{-1}
+ *
+ * @param[in] isot The isotopologue
+ * @return A function J -> E(J) in Joule
+ */
+std::function erot_selection(const SpeciesIsotope& isot) {
+ // NH3 main isotopologue (14N-1H3)
+ if (isot == "NH3-4111"_isot) {
+ return [](const Rational J) -> Numeric {
+ return Conversion::kaycm2joule(9.9402) * Numeric(J * (J + 1));
+ };
+ }
+
+ // PH3 main isotopologue (31P-1H3)
+ if (isot == "PH3-1111"_isot) {
+ return [](const Rational J) -> Numeric {
+ return Conversion::kaycm2joule(4.4522) * Numeric(J * (J + 1));
+ };
+ }
+
+ ARTS_USER_ERROR("{} has no rotational energies for symmetric top ECS in ARTS",
+ isot.FullName())
+ return [](const Rational J) -> Numeric {
+ return Numeric(J) * std::numeric_limits::signaling_NaN();
+ };
+}
+} // namespace
+
+Numeric reduced_dipole(const Rational Jf, const Rational Ji, const Rational K) {
+ // d(Jf, Ji, K) = (-1)^{K+Jf} sqrt(2*Jf+1) * 3j(Jf, 1, Ji; K, 0, -K)
+ // This is identical in structure to Eq. (10) of Rodrigues et al. 1997
+ // with K replacing the vibrational angular momentum l.
+ if (not iseven(Jf + K + 1))
+ return -sqrtr(2 * Jf + 1) *
+ wigner3j(Jf, Rational{1}, Ji, K, Rational{0}, -K);
+ return +sqrtr(2 * Jf + 1) * wigner3j(Jf, Rational{1}, Ji, K, Rational{0}, -K);
+}
+
+void relaxation_matrix_offdiagonal(MatrixView& W,
+ const QuantumIdentifier& bnd_qid,
+ const band_data& bnd,
+ const ArrayOfIndex& sorting,
+ const SpeciesEnum broadening_species,
+ const linemixing::species_data& rovib_data,
+ const Vector& dipr,
+ const AtmPoint& atm) {
+ using Conversion::kelvin2joule;
+
+ const Size n = bnd.size();
+ if (not n) return;
+
+ // K is a line-level quantum number for symmetric tops.
+ // Lines with different K are not coupled (ΔK=0 collisions only).
+ // Read K for each line and find the maximum K for Wigner init.
+ std::vector Kvec(n);
+ for (Size i = 0; i < n; i++) {
+ Kvec[i] = bnd.lines[sorting[i]].qn.at(QuantumNumberType::K).lower;
+ }
+
+ const Numeric T = atm.temperature;
+
+ const auto erot = erot_selection(bnd_qid.isot);
+
+ const std::array rats{bnd.max(QuantumNumberType::J),
+ bnd.max(QuantumNumberType::K)};
+ const int maxL = wigner_init_size(rats);
+
+ const auto Om = [&]() {
+ Vector out(maxL);
+ for (Index i = 0; i < maxL; i++)
+ out[i] = rovib_data.Omega(atm.temperature,
+ bnd.front().ls.T0,
+ broadening_species == SpeciesEnum::Bath
+ ? atm.mean_mass()
+ : atm.mean_mass(broadening_species),
+ bnd_qid.isot.mass,
+ erot(Rational{i}),
+ erot(Rational{i - 2}));
+ return out;
+ }();
+
+ const auto Q = [&]() {
+ Vector out(maxL);
+ for (Index i = 0; i < maxL; i++)
+ out[i] = rovib_data.Q(
+ Rational{i}, atm.temperature, bnd.front().ls.T0, erot(Rational{i}));
+ return out;
+ }();
+
+ // The coupling for symmetric tops with ΔK=0 is identical to the
+ // linear molecule (Hartmann) case with K replacing l:
+ //
+ // R(J→J'; K) = (-1)^{J+J'+1} (2J'+1) Ω(J)
+ // × Σ_L (2L+1) 3j(J, J', L; K, -K, 0) × 3j(Jf, Jf', L; K, -K, 0)
+ // × 6j(Ji, Jf, 1; Jf', Ji', L) × Q(L)/Ω(L)
+ //
+ // For pure rotational ΔK=0 transitions: Ji = J (upper), Jf = J±1 (lower).
+ // Ki = Kf = K is constant for the entire sub-band.
+
+ arts_wigner_thread_init(maxL);
+ for (Size i = 0; i < n; i++) {
+ auto& J = bnd.lines[sorting[i]].qn.at(QuantumNumberType::J);
+ const Rational Ji = J.upper;
+ const Rational Jf = J.lower;
+ const Rational Ki = Kvec[i];
+
+ for (Size j = 0; j < n; j++) {
+ if (i == j) continue;
+
+ // Only couple lines within the same K sub-band
+ if (Kvec[j] != Ki) continue;
+
+ auto& J_p = bnd.lines[sorting[j]].qn.at(QuantumNumberType::J);
+ const Rational Ji_p = J_p.upper;
+ const Rational Jf_p = J_p.lower;
+
+ // Only compute the downward element (J' ≤ J)
+ if (Jf_p > Jf) continue;
+
+ Index L = std::max(std::abs((Ji - Ji_p).toIndex()),
+ std::abs((Jf - Jf_p).toIndex()));
+ L += L % 2;
+ const Index Lf = std::min((Ji + Ji_p).toIndex(), (Jf + Jf_p).toIndex());
+
+ Numeric sum = 0;
+ for (; L <= Lf; L += 2) {
+ const Numeric a = wig3(Ji, Ji_p, Rational{L}, Ki, -Ki, Rational{0});
+ const Numeric b = wig3(Jf, Jf_p, Rational{L}, Ki, -Ki, Rational{0});
+ const Numeric c = wig6(Ji, Jf, Rational{1}, Jf_p, Ji_p, Rational{L});
+ sum += a * b * c * Numeric(2 * L + 1) * Q[L] / Om[L];
+ }
+ const Numeric ECS = Om[Ji.toIndex()];
+ const Numeric scl =
+ ECS * Numeric(2 * Ji_p + 1) * sqrtr((2 * Jf + 1) * (2 * Jf_p + 1));
+ sum *= scl;
+
+ // Downward element and detailed balance for upward
+ W[j, i] = sum;
+ W[i, j] = sum * std::exp((erot(Jf_p) - erot(Jf)) / kelvin2joule(T));
+ }
+ }
+ arts_wigner_thread_free();
+
+ ARTS_USER_ERROR_IF(errno == EDOM, "Cannot compute the wigner symbols")
+
+ // Sum rule correction (same as Hartmann, but within K sub-bands)
+ for (Size i = 0; i < n; i++) {
+ Numeric sumlw = 0.0;
+ Numeric sumup = 0.0;
+ const Rational Ki = Kvec[i];
+
+ for (Size j = 0; j < n; j++) {
+ if (Kvec[j] != Ki) continue; // Only within same K sub-band
+
+ if (j > i) {
+ sumlw += dipr[j] * W[j, i];
+ } else {
+ sumup += dipr[j] * W[j, i];
+ }
+ }
+
+ const Rational Ji = bnd.lines[sorting[i]].qn.at(QuantumNumberType::J).lower;
+ for (Size j = i + 1; j < n; j++) {
+ if (Kvec[j] != Ki) continue; // Only within same K sub-band
+
+ const Rational Jj =
+ bnd.lines[sorting[j]].qn.at(QuantumNumberType::J).lower;
+ if (std::abs(sumlw) <= std::abs(sumup) * 1e-6) {
+ // The sum rule correction would amplify off-diagonal elements
+ // excessively. This can happen for Q-branch (ΔJ=0) transitions
+ // where the coupling structure differs from P/R branches.
+ // Zero the elements to prevent NaN propagation.
+ W[j, i] = 0.0;
+ W[i, j] = 0.0;
+ } else {
+ W[j, i] *= -sumup / sumlw;
+ W[i, j] = W[j, i] * std::exp((erot(Ji) - erot(Jj)) / kelvin2joule(T));
+ }
+ }
+ }
+}
+} // namespace lbl::voigt::ecs::stotop
diff --git a/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.h b/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.h
new file mode 100644
index 0000000000..4977fa4533
--- /dev/null
+++ b/src/core/lbl/lbl_lineshape_voigt_ecs_stotop.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include
+
+#include "lbl_data.h"
+#include "lbl_lineshape_linemixing.h"
+
+namespace lbl::voigt::ecs::stotop {
+/*! Returns the reduced dipole for a symmetric top molecule
+ *
+ * For symmetric top pure rotational transitions (ΔK=0, Δv=0),
+ * the reduced dipole matrix element is:
+ *
+ * d(Jf, Ji, K) = (-1)^{Jf+K+1} sqrt(2*Jf+1) * 3j(Jf, 1, Ji; K, 0, -K)
+ *
+ * @param[in] Jf Lower state total angular momentum
+ * @param[in] Ji Upper state total angular momentum
+ * @param[in] K Projection of angular momentum on molecular axis (same for
+ * upper and lower)
+ * @return The reduced dipole
+ */
+Numeric reduced_dipole(const Rational Jf,
+ const Rational Ji,
+ const Rational K);
+
+/*! Compute the off-diagonal elements of the relaxation matrix
+ * for symmetric top molecules (NH3, PH3, etc.)
+ *
+ * Uses the ECS-EP (Energy Corrected Sudden with Exponential Power law)
+ * formalism. The coupling between lines ℓ and ℓ' within a K-sub-band
+ * involves 3j and 6j symbols with K replacing the vibrational angular
+ * momentum quantum number l used in the linear molecule (CO2) case.
+ * Lines with different K are not coupled (ΔK=0 collisions).
+ * K is read from each line's quantum numbers.
+ *
+ * @param[in,out] W Relaxation matrix (diagonal already set)
+ * @param[in] bnd_qid Band quantum identifier
+ * @param[in] bnd Band data (lines carry K quantum number)
+ * @param[in] sorting Index sorting of the lines
+ * @param[in] broadening_species Which broadening species to use
+ * @param[in] rovib_data ECS-EP parameters for the species
+ * @param[in] dipr Reduced dipole moments
+ * @param[in] atm Atmospheric point
+ */
+void relaxation_matrix_offdiagonal(MatrixView& W,
+ const QuantumIdentifier& bnd_qid,
+ const band_data& bnd,
+ const ArrayOfIndex& sorting,
+ const SpeciesEnum broadening_species,
+ const linemixing::species_data& rovib_data,
+ const Vector& dipr,
+ const AtmPoint& atm);
+} // namespace lbl::voigt::ecs::stotop
diff --git a/src/core/lbl/lbl_voigt.cpp b/src/core/lbl/lbl_voigt.cpp
index d92359ce07..5db90f290d 100644
--- a/src/core/lbl/lbl_voigt.cpp
+++ b/src/core/lbl/lbl_voigt.cpp
@@ -9,6 +9,8 @@ bool is_voigt(LineByLineLineshape lsm) {
switch (lsm) {
case VP_ECS_HARTMANN:
case VP_ECS_MAKAROV:
+ case VP_ECS_STOTOP:
+ case VP_ECS_SPHTOP:
case VP_LTE:
case VP_LINE_NLTE:
case VP_LTE_MIRROR: return true;
diff --git a/src/core/matpack/rational.h b/src/core/matpack/rational.h
index 05117f29cc..01e63a464c 100644
--- a/src/core/matpack/rational.h
+++ b/src/core/matpack/rational.h
@@ -248,7 +248,18 @@ struct Rational {
return Rational(std::forward(a)) % b;
}
- constexpr auto operator<=>(const Rational& b) const noexcept = default;
+ constexpr auto operator<=>(const Rational& b) const noexcept {
+ //! REMINDER: This code works because GCD is guaranteed to have a positive denom
+ return (denom == b.denom) ? (numer <=> b.numer) : [*this, b] {
+ Index r1 = numer / denom;
+ Index r2 = b.numer / b.denom;
+ auto res2 = r1 <=> r2;
+ return res2 != std::strong_ordering::equal
+ ? res2
+ : ((numer % denom) * b.denom) <=>
+ ((b.numer % b.denom) * denom);
+ }();
+ };
constexpr bool operator==(const Rational& b) const noexcept {
return numer == b.numer and denom == b.denom;
@@ -266,7 +277,7 @@ struct Rational {
template
friend constexpr auto operator<=>(T&& b, const Rational& a) noexcept {
- return a <=> Rational{std::forward(b)};
+ return Rational{std::forward(b)} <=> a;
}
template
diff --git a/src/core/options/arts_options.cc b/src/core/options/arts_options.cc
index 687d210399..2b2e8b006d 100644
--- a/src/core/options/arts_options.cc
+++ b/src/core/options/arts_options.cc
@@ -648,7 +648,13 @@ parameters that are mapped to the species identifier.
"Voigt using Makarov's method of error-corrected sudden for line mixing of O2"},
Value{
"VP_ECS_HARTMANN",
- "Voigt using Hartmann's method of error-corrected sudden for line mixing of CO2"}},
+ "Voigt using Hartmann's method of error-corrected sudden for line mixing of CO2"},
+ Value{
+ "VP_ECS_STOTOP",
+ "[WIP] [UNTESTED] Voigt using error-corrected sudden for line mixing of symmetric top molecules (NH3, PH3)"},
+ Value{
+ "VP_ECS_SPHTOP",
+ "[WIP] [UNTESTED] Voigt using error-corrected sudden for line mixing of spherical top molecules (CH4)"}},
});
opts.emplace_back(EnumeratedOption{
diff --git a/src/core/tests/test_laginterp.cpp b/src/core/tests/test_laginterp.cpp
index 0a3b65c75f..a0ff8b15ec 100644
--- a/src/core/tests/test_laginterp.cpp
+++ b/src/core/tests/test_laginterp.cpp
@@ -584,10 +584,10 @@ int main() {
print_time_points();
- test_variant_lag<0>();
- test_variant_lag<1>();
- test_variant_lag<2>();
- test_variant_lag<3>();
- test_variant_lag<4>();
- test_variant_lag<5>();
+ test_variant_lag<0z>();
+ test_variant_lag<1z>();
+ test_variant_lag<2z>();
+ test_variant_lag<3z>();
+ test_variant_lag<4z>();
+ test_variant_lag<5z>();
}
diff --git a/src/m_linemixing.cc b/src/m_linemixing.cc
index 509b5cee97..f7abb390cc 100644
--- a/src/m_linemixing.cc
+++ b/src/m_linemixing.cc
@@ -131,3 +131,91 @@ void abs_ecs_dataAddTran2011(LinemixingEcsData& abs_ecs_data) {
data(T0, {Conversion::angstrom2meter(5.5)});
}
}
+
+void abs_ecs_dataAddNH3(LinemixingEcsData& abs_ecs_data) {
+ ARTS_TIME_REPORT
+
+ using enum LineShapeModelType;
+ using data = lbl::temperature::data;
+
+ auto& ecs = abs_ecs_data["NH3-4111"_isot];
+
+ // H2 broadening parameters
+ auto& h2 = ecs[SpeciesEnum::Hydrogen];
+ h2.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.040), 0.73});
+ h2.lambda = data(T0, {0.65});
+ h2.beta = data(T0, {0.006});
+ h2.collisional_distance = data(T0, {Conversion::angstrom2meter(2.3)});
+
+ // He broadening parameters
+ auto& he = ecs[SpeciesEnum::Helium];
+ he.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.018), 0.55});
+ he.lambda = data(T0, {0.58});
+ he.beta = data(T0, {0.003});
+ he.collisional_distance = data(T0, {Conversion::angstrom2meter(1.8)});
+
+ auto& nh3 = ecs[SpeciesEnum::Ammonia];
+ nh3.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.040), 0.73});
+ nh3.lambda = data(T0, {0.65});
+ nh3.beta = data(T0, {0.006});
+ nh3.collisional_distance = data(T0, {Conversion::angstrom2meter(2.3)});
+}
+
+void abs_ecs_dataAddPH3(LinemixingEcsData& abs_ecs_data) {
+ ARTS_TIME_REPORT
+
+ using enum LineShapeModelType;
+ using data = lbl::temperature::data;
+
+ auto& ecs = abs_ecs_data["PH3-1111"_isot];
+
+ // H2 broadening parameters (approximate, based on similarity to NH3)
+ auto& h2 = ecs[SpeciesEnum::Hydrogen];
+ h2.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.035), 0.70});
+ h2.lambda = data(T0, {0.60});
+ h2.beta = data(T0, {0.005});
+ h2.collisional_distance = data(T0, {Conversion::angstrom2meter(2.5)});
+
+ // He broadening parameters (approximate)
+ auto& he = ecs[SpeciesEnum::Helium];
+ he.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.015), 0.50});
+ he.lambda = data(T0, {0.55});
+ he.beta = data(T0, {0.003});
+ he.collisional_distance = data(T0, {Conversion::angstrom2meter(2.0)});
+
+ // PH3 self-broadening parameters (approximate, based on H2 values)
+ auto& ph3 = ecs[SpeciesEnum::Phosphine];
+ ph3.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.035), 0.70});
+ ph3.lambda = data(T0, {0.60});
+ ph3.beta = data(T0, {0.005});
+ ph3.collisional_distance = data(T0, {Conversion::angstrom2meter(2.5)});
+}
+
+void abs_ecs_dataAddCH4(LinemixingEcsData& abs_ecs_data) {
+ ARTS_TIME_REPORT
+
+ using enum LineShapeModelType;
+ using data = lbl::temperature::data;
+
+ auto& ecs = abs_ecs_data["CH4-211"_isot];
+
+ // H2 broadening parameters
+ auto& h2 = ecs[SpeciesEnum::Hydrogen];
+ h2.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.060), 0.75});
+ h2.lambda = data(T0, {0.70});
+ h2.beta = data(T0, {0.008});
+ h2.collisional_distance = data(T0, {Conversion::angstrom2meter(2.4)});
+
+ // He broadening parameters
+ auto& he = ecs[SpeciesEnum::Helium];
+ he.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.025), 0.56});
+ he.lambda = data(T0, {0.60});
+ he.beta = data(T0, {0.004});
+ he.collisional_distance = data(T0, {Conversion::angstrom2meter(1.9)});
+
+ auto& ch4 = ecs[SpeciesEnum::Methane];
+ ch4.scaling = data(T1, {Conversion::kaycm_per_atm2hz_per_pa(0.060), 0.75});
+ ch4.lambda = data(T0, {0.70});
+ ch4.beta = data(T0, {0.008});
+ ch4.collisional_distance = data(T0, {Conversion::angstrom2meter(2.4)});
+}
diff --git a/src/workspace_methods.cpp b/src/workspace_methods.cpp
index 97e5f183c2..47d963ae9b 100644
--- a/src/workspace_methods.cpp
+++ b/src/workspace_methods.cpp
@@ -1055,6 +1055,36 @@ This is based on the work of :cite:t:`Rodrigues1997`.
.in = {"abs_ecs_data"},
};
+ wsm_data["abs_ecs_dataAddNH3"] = {
+ .desc = R"--(Sets preliminary NH3-4111 band data for ECS.
+
+[WIP] [UNTESTED]
+)--",
+ .author = {"Richard Larsson"},
+ .out = {"abs_ecs_data"},
+ .in = {"abs_ecs_data"},
+ };
+
+ wsm_data["abs_ecs_dataAddPH3"] = {
+ .desc = R"--(Sets preliminary PH3-1111 band data for ECS.
+
+[WIP] [UNTESTED]
+)--",
+ .author = {"Richard Larsson"},
+ .out = {"abs_ecs_data"},
+ .in = {"abs_ecs_data"},
+ };
+
+ wsm_data["abs_ecs_dataAddCH4"] = {
+ .desc = R"--(Sets preliminary CH4-211 band data for ECS.
+
+[WIP] [UNTESTED]
+)--",
+ .author = {"Richard Larsson"},
+ .out = {"abs_ecs_data"},
+ .in = {"abs_ecs_data"},
+ };
+
wsm_data["abs_ecs_dataInit"] = {
.desc = R"--(Resets/initializes the ECS data.
)--",