Conventions

Quick start summarizes the conventions that matter most; this page lists them all.

Scattering

  • Refractive index. \(m = n + ik\), where \(\operatorname{Im} m \ge 0\) means absorbing (the Bohren–Huffman and Mishchenko convention, time dependence \(e^{-i\omega t}\)). Negative \(\operatorname{Im} m\) is rejected with InvalidInput; it is never silently flipped. Codes using \(e^{+i\omega t}\), such as Wiscombe’s MIEV0, write \(m = n - ik\) and report the complex conjugates of the coefficients and amplitudes. The refractive index is that of the particle relative to the surrounding medium.

  • Size parameter. \(x = 2\pi r/\lambda\), with \(\lambda\) the wavelength in the medium. Derivatives are with respect to \(x\); multiply by \(2\pi/\lambda\) for the radius.

  • Lengths. The Mie functions take the size parameter and return efficiencies, cross sections divided by \(\pi r^2\). The ensemble functions take radii and wavelengths in any unit, provided both use the same one, and return cross sections per particle in that unit squared.

  • Coefficients. \(a_n\), \(b_n\) for \(n = 1, \ldots, N\) (Bohren & Huffman 1983, Eq. 4.53), stored at index \(n - 1\).

  • Amplitudes. \(S_1(\mu)\) and \(S_2(\mu)\) at \(\mu = \cos\Theta\) (Bohren & Huffman 1983, Eq. 4.74).

  • Phase-matrix normalization. \(\tfrac12 \int_{-1}^{1} F_{11}(\mu)\,d\mu = 1\), with \(F_{11} = (\lvert S_1\rvert^2 + \lvert S_2\rvert^2)/(x^2 Q_\text{sca}/2)\). The unnormalized phase matrix is the normalized one times \(Q_\text{sca}\) (per unit geometric cross section) for single spheres, and times \(\langle C_\text{sca}\rangle\) (per particle) for ensembles.

  • Phase-matrix elements. \(F_{11}\), \(F_{12}\), \(F_{33}\), \(F_{34}\) are Bohren & Huffman’s \(S_{11}\), \(S_{12}\), \(S_{33}\), \(S_{34}\) (1983, §4.4) times that normalization, with \(F_{22} = F_{11}\) and \(F_{44} = F_{33}\) for spheres. \(F_{12} < 0\) for Rayleigh scattering at 90°, so the degree of linear polarization of scattered unpolarized light is \(-F_{12}/F_{11}\).

  • Greek coefficients. \(\alpha_1, \ldots, \alpha_4\), \(\beta_1\), \(\beta_2\) expand the phase matrix in the Wigner d-functions of wigner_d at \(\mu = \cos\Theta\) (Mishchenko, Travis & Lacis 2002, Eqs. 4.75–4.80), indexed by order \(l = 0, \ldots, L\). Normalized, \(\alpha_1^0 = 1\) and \(\alpha_1^1 = 3g\). sasktran2’s coefficients have the same \(\alpha\) and the opposite sign of \(\beta_1\) and \(\beta_2\). See Homogeneous spheres (Lorenz–Mie) for the expansion and the sign conventions.

  • Derivatives with respect to m. The coefficients and amplitudes are holomorphic in \(m\): their derivative \(d/dm\) equals \(\partial/\partial(\operatorname{Re} m)\), and \(\partial/\partial(\operatorname{Im} m) = i\,d/dm\). Real outputs give \(\partial/\partial(\operatorname{Re} m)\) and \(\partial/\partial(\operatorname{Im} m)\).

Errors

The Rust core returns one error type, scatrs::Error, and Python raises a matching exception. The Python exceptions are also subclasses of the standard exception for the same kind of failure:

Rust scatrs::Error

Python exception

Meaning

InvalidInput

InvalidInputError (also ValueError)

an argument is outside its domain or a documented limit

NotConverged { achieved, requested }

NotConvergedError (also ArithmeticError)

an iteration missed the accuracy it promises

OutsideValidatedDomain

OutsideValidatedDomainError (also ValueError)

valid inputs outside the domain where accuracy has been validated

All three derive from ScatrsError. The Mie and ensemble functions raise OutsideValidatedDomain for valid inputs outside their validated domain, unless the caller allows it (allow_unvalidated). Python raises the standard exceptions where Python itself would: TypeError for a wrong argument type, for example. See scatrs.errors.

Kernels such as gauss_legendre and wigner_d check their inputs and return an error rather than panic or return NaN. The scalar types instead follow f64: a function outside its domain returns NaN, as f64::sqrt(-1.0) does.

Special functions

Wigner d-functions. scatrs uses the convention of Mishchenko, Travis & Lacis (2002), Scattering, Absorption, and Emission of Light by Small Particles, Appendix B. It is the common \(d^j_{m'm}(\beta)\) with \((m', m)\) renamed \((m, n)\). For example

\[d^1_{01}(\theta) = \frac{\sin\theta}{\sqrt2}, \qquad d^1_{10}(\theta) = -\frac{\sin\theta}{\sqrt2}, \qquad d^2_{22}(\theta) = \cos^4\frac\theta2 .\]

The functions take \(x = \cos\theta \in [-1, 1]\) and return every degree \(l = 0, \ldots, l_\text{max}\). Entries with \(l < \max(\lvert m\rvert, \lvert n\rvert)\) are zero. In Python the degree is the last axis: wigner_d() returns an array of shape np.shape(x) + (l_max + 1,). See Special functions.

Quadrature. Gauss–Legendre rules are on \([-1, 1]\). Nodes ascend and the rule is exactly symmetric: nodes[i] == -nodes[n - 1 - i] and weights[i] == weights[n - 1 - i]. For odd \(n\) the middle node is \(+0\).

Determinism

Each Mie kernel runs on one thread, and the ensembles give the same bits for any number of threads, so a given build gives the same bits for the same inputs every time. Gauss–Legendre rules are designed to be bitwise identical across platforms too, because they use no platform maths library. Some results do use the platform’s library and can differ between platforms in the last bits, within their accuracy bounds:

  • f64 elementary functions such as exp and sin, including those inside Dual<f64, N>;

  • the double-double functions that start from an f64 result (ln, cbrt and atan2), and those computed from them, such as powf, asin and acos;

  • Dual<f64, N> sin and cos on Apple targets;

  • the Mie outputs, which use f64 sin and cos. The default number of terms uses cbrt, so where \(x + 6x^{1/3} + 3\) is an integer (at \(x = 8\), for example) a platform whose cbrt is not correctly rounded can use one term more or fewer.

Rust

Mixed arithmetic. Generic code over T: Real combines T with f64 with the f64 on the right: x * 2.0, not 2.0 * x. For Complex<T>, write z.scale(T::from_f64(2.0)).

Derivatives. Dual<T, N> carries \(N\) first-order derivatives alongside a value. The value is always computed bit for bit as T would compute it; sin and cos are the one exception (see below). The conventions that affect results are:

  • Comparisons (==, <, …) look only at the value. is_zero and is_one are strict: every derivative must be zero too.

  • A derivative component that is exactly zero is a strong zero. It stays zero even when multiplied by an infinite or NaN factor.

  • At points where a function is not differentiable, scatrs makes a fixed choice. For example, hypot at the origin has derivative 0 and atan2 at the origin has NaN derivatives.

  • On Apple targets the f64 values of sin and cos come from one fused sincos call.

Scalar types (Rust) gives the reasons and the main rules; the rustdoc of Dual lists them all.