Limitations and known issues¶
This page lists what users should know: what scatrs does not cover yet, what calculations cost, and how results behave at the edges of the validated domain. How scatrs is validated describes what the accuracy figures rest on.
Scope and availability¶
scatrs computes homogeneous spheres and layered spheres (a core and concentric shells), single or averaged over size distributions and external mixtures, and randomly oriented spheroids, single.
Python exposes only
f64results. Double-double precision and theDualderivatives of the Rust core are available from Rust only.The tests run on Linux (x86_64), macOS (arm64) and Windows (x86_64) for every change. The slowest tests run weekly, on the same three systems: the 20 000-point double-double Gauss–Legendre rule against Arb, every asymptotic rule from 101 to 3000 points against double-double, and the phase matrix and Greek coefficients of spheres at \(x = 2\cdot10^3\), \(10^4\) and \(2\cdot10^4\). The released wheels are also tested on aarch64 Linux and Windows and on Intel macOS; the s390x, ppc64le and musl Linux wheels are built but not tested. 32-bit platforms are not supported.
Layered spheres¶
Concentric layers only. Inclusions off the centre, and non-spherical layers, need the T-matrix method, which is planned.
Validated domain. \(10^{-6} \le x_L \le 10^4\) with \(|m_l|\,x_L \le 3\cdot10^4\) for every layer, every index in the sphere’s domain, and at most 16 layers. Beyond \(|m|\,x_L = 3\cdot10^4\) the narrow resonances of thin lossless shells of high index cost the coefficients up to \(10^9\) ulps. A single layer is a sphere, with the sphere’s domain.
Derivatives up to \(x_L = 1000\). Above it the derivatives lose accuracy faster than the values for thin lossless shells (the forward amplitude’s derivative with respect to the index of a thin air shell around a large core reached errors of \(2\cdot10^{-8}\) relative at \(x_L = 10^4\)). Above \(x_L = 1000\),
compute_with_derivativesandderivatives=raiseOutsideValidatedDomainunlessallow_unvalidatedis set.Cost. About 2.5 times a sphere of the outer size for a coated sphere, and roughly 1.5 times more per further layer (Layered spheres).
Fixed fractions in ensembles. A size distribution of layered spheres scales the whole particle: each interface stays at a fixed fraction of the outer radius. A coating of fixed thickness across the sizes, and fractions or layer indices as grid axes, are not available.
Ensemble derivatives up to \(x_L = 1000\). The derivatives of an ensemble of layered spheres with respect to their indices and fractions, and the radius derivative of a monodisperse one, need every size node within the derivatives’ domain: a distribution that reaches a larger outer size parameter raises
OutsideValidatedDomainunlessallow_unvalidatedis set. Its values, and its derivatives with respect to the distribution’s own parameters, are validated up to \(10^4\).
Spheroids¶
Random orientation only. Fixed orientations, distributions of aspect ratio (other than as mixtures of components of one aspect ratio each), and other axisymmetric shapes (cylinders, Chebyshev particles) are not yet available.
Ensembles cost far more than spheres’. Every size node solves a T-matrix, milliseconds at small sizes and seconds near \(|m|\,x_{\max} = 50\), and the tails of a broad distribution reach large sizes: the tighter
rtol, the further. With \(r_g = 0.06\) µm and \(\sigma_g = 1.6\) at 1 µm, the upper end of the integration is \(x = 10\) atrtol\(= 10^{-4}\) and \(x = 20\) at \(10^{-8}\), past the validated domain for an aspect ratio of 1/3. A loosertol(\(10^{-4}\) to \(10^{-3}\) is ample for radiative transfer) keeps broad distributions practical (Ensembles).Validated domain. Aspect ratios from 1/5 to 5, equal-volume \(x \ge 10^{-6}\), \(|m|\,x_{\max} \le 60\) with \(x_{\max}\) the size parameter of the longest semi-axis, \(1.3 \le \mathrm{Re}\,m \le 3\), \(\mathrm{Im}\,m \le 0.5\), and
rtoldown to \(10^{-12}\) (Internal validation). The edge in \(|m|\,x_{\max}\) is set by cost, not accuracy.Cost. It grows like \(x_{\max}^4\), and much faster where the modes need mixed-precision refinement, which is most often for prolate spheroids and at the largest \(|m|\,x_{\max}\). At
rtol = 1e-8on one core an oblate spheroid with \(\varepsilon = 2\) takes about 22 ms at \(x = 5\) and 2 s at \(x = 15\); \(\varepsilon = 1/5\) at \(x = 12\) takes about a minute. The quadrature runs in parallel on several cores.precision="f64"skips the refinement: it is fastest, and the error estimate still reports what it costs in accuracy.Derivatives, all four together, cost 4 to 8 times the values.
Mie scattering¶
Validated domain. \(10^{-6} \le x \le 2\cdot10^4\), \(0 < \operatorname{Re} m \le 10\), \(0 \le \operatorname{Im} m \le 10\), \(\lvert m\rvert \ge 0.1\) (below 0.1, \(Q_\text{abs}\) and the Jacobian of nearly real \(m\) lose accuracy). Outside it, calls raise
OutsideValidatedDomainunlessallow_unvalidatedis set. Accepted inputs extend to \(x \in [10^{-30}, 10^6]\) and \(\lvert m\rvert \in [10^{-6}, 10^3]\), where the outputs are finite but their accuracy is not validated. The number of terms is at mostMAX_MIE_ORDER= \(2^{20}\).Accuracy near resonances. The declared accuracy is relative to the conditioning of each output. Near the sharp resonances of weakly absorbing and lossless spheres the outputs are very sensitive to \(x\) and \(m\), and their absolute errors grow accordingly. See Internal validation.
Truncation and narrow resonances. The default truncation,
ceil(x + 6 x**(1/3) + 3)terms, can miss an exponentially narrow whispering-gallery resonance of a high-index, weakly absorbing sphere above that order. The size parameter must fall in an exponentially small window to meet one. Pass a largern_maxto include higher orders. See Homogeneous spheres (Lorenz–Mie).Absorption derivatives. \(\partial Q_\text{abs}/\partial x\) and \(\partial Q_\text{abs}/\partial(\operatorname{Re} m)\) of weakly absorbing spheres are accurate relative to \(Q_\text{ext}\), not to \(Q_\text{abs}\) itself: near a resonance with \(k = 10^{-8}\) their relative error reached \(4\cdot10^{-9}\).
m = 1 exactly. The coefficients vanish, but for \(x \ge 1\) the lowest orders are computed as rounding noise rather than exact zeros, so \(g\) and its derivatives are meaningless there (the efficiencies are at rounding level).
Cost. \(O(x + \lvert m\rvert x)\) per sphere; the Python functions of
scatrs.mieloop over the spheres on one thread. The largest accepted \(\lvert m\rvert x\) take seconds per sphere, and a call cannot be interrupted.Cost of the Greek coefficients. All orders of one sphere cost \(O(x^2)\): 7.6 s at \(x = 10^4\) in
f64on an Apple M4 Pro. Ask for fewer orders (order=) where a calculation does not need them all. All orders also need \(2N + 1\) quadrature nodes, at most \(2^{17}\), sogreekrejects spheres with \(N \ge 2^{16}\) (\(x\) above about \(6.5\cdot10^4\), outside the validated domain) unless asked for fewer orders.Node rounding in the Greek coefficients. The
f64Gauss–Legendre nodes are rounded, and the error this causes grows like \(L^2 + x^2\) for order \(L\): measured, about \(2\cdot10^{-9}\) of \(\max_l\lvert\alpha_1^l\rvert\) at \(x = 2\cdot10^4\) with \(L = 64\), and \(2\cdot10^{-11}\) at \(x = 10^3\) for all orders. The declared accuracy includes it. See The accuracy model.Validation of large spheres. The accuracy map of the phase matrix and the Greek coefficients stops at \(x = 10^3\); larger spheres are checked weekly at three sizes, \(x = 2\cdot10^3\), \(10^4\) and \(2\cdot10^4\), the last two only to order 64. See The phase matrix and the Greek coefficients.
Ensembles of spheres¶
Check
converged. A size integration that cannot meetrtolwithinmax_panelspanels returns its values withconverged = falserather than raising.Non-absorbing spheres. Without absorption the efficiencies of large spheres have narrow resonance ripples that the size integration may not resolve within
max_panels(default 4096). For a lognormal with \(r_g = 1\) µm, \(\sigma_g = 2\) and \(m = 1.33\) at \(\lambda = 0.5\) µm, the result hasconverged = falseat 4096 panels and an estimate of about \(10^{-5}\) of \(\langle C_\text{ext}\rangle\). A little absorption, or a largermax_panels, helps, though withmax_panelsraised the estimate of the converged result, \(9.7\cdot10^{-7}\) of \(\langle C_\text{ext}\rangle\), fell short of its true error, \(1.1\cdot10^{-6}\). See Ensembles.Derivatives with respect to \(\mathrm{Im}\,m\) at \(\mathrm{Im}\,m = 0\) have a spike at every resonance and do not converge (
converged = false); the values do.Backscattering is not refined by default. The estimates of \(\langle C_\text{back}\rangle\) and the lidar ratio are indicative unless
refine_backscatteris set, which can cost many more panels for weakly absorbing spheres. See Ensembles.⟨C_abs⟩ of weak absorbers. ⟨C_abs⟩ is refined to rtol of ⟨C_ext⟩, so for weak absorbers its relative accuracy, and that of the co-albedo, is far worse than rtol, and its estimate is indicative: for the database grid of Comparison with sasktran2 (\(m = 1.43 + 10^{-8}i\)) it fell up to 1.6 times short of the true error. The single-scattering albedo itself is accurate to about rtol, and its estimate is conservative.
Validated domain. Every size node must be in the validated domain of the Mie solution; the tails of broad distributions can reach \(x > 2\cdot10^4\). Outside it, calls raise
OutsideValidatedDomain(naming the wavelength and the radius) unlessallow_unvalidatedis set. A range that reaches \(x > 10^6\), beyond any Mie solution, raisesInvalidInput.No scattering, no ratios. A zero or non-finite \(\langle C_\text{ext}\rangle\), \(\langle C_\text{sca}\rangle\) or \(\langle C_\text{back}\rangle\) (for \(m = 1\), say) raises
InvalidInput: the albedo, the asymmetry parameter and the lidar ratio are undefined.Cost of the Greek coefficients. The exact Greek coefficients need each size node’s amplitudes at about \(N + L/2\) cosines, \(O(N^2)\) for large spheres, whatever the order \(L\). Measured on an Apple M4 Pro (12 cores), per wavelength, a lognormal with \(r_g = 1\) µm, \(\sigma_g = 2\) and \(m = 1.33 + 10^{-4}i\) at rtol \(10^{-6}\): the cross sections, \(g\) and the moments take 0.037 s, 0.11 s and 0.20 s at \(\lambda\) = 1, 0.5 and 0.3 µm; with the Greek coefficients to order 32, 0.093 s, 0.31 s and 0.90 s. See Ensembles and Comparison with sasktran2.
Grids and lists compute values only. A grid of distributions (array-valued parameters) or a list of distributions shares its size nodes; derivatives come from an
Ensembleof one distribution at a time. AMixturetakes ensembles of one distribution each, not grids or lists.Memory of grids. Each distribution of a grid holds sums over every band’s tables: about 2.5 MB per distribution and wavelength in flight at \(\lambda = 0.28\) µm for the database grid of Comparison with sasktran2 (2 GB for 800 distributions on one thread; more wavelengths run at once on more threads). Split very large grids.
Tables can exceed
max_panels. Every radius of aTabulateddistribution is a panel edge, so a table of more radii thanmax_panelsstill takes more panels thanmax_panels(and converges).Distributions over size only. The particles are spheres, so the distributions are over their radius only.
Threads. The computation runs on all cores, through rayon’s global thread pool. Python has no call to set the number of threads; set the environment variable
RAYON_NUM_THREADSbefore the first call (in Rust, a caller caninstallits own pool).f64only. The ensemble engine is not generic overReal: no double-double orDualensembles. Its derivatives come from the closed-form single-sphere derivatives instead.
Python bindings¶
Shared inputs. Calls release the GIL and read input arrays in place. Do not modify or resize an input array from another thread during a call.
Output size. One Mie call returns at most
MAX_MIE_VALUES= \(2^{27}\) complex values, counting derivatives (2 GiB), or twice as many real values for the efficiencies, the phase matrix and the Greek coefficients. The bound applies to the broadcast output, which small inputs can make large: a call above it raisesInvalidInputErrorbefore allocating anything.Per-call overhead. About 1 µs per call (a one-off measurement on an Apple M4 Pro): prefer one call on a large array to many small calls.
Rust API¶
The rest of this page concerns the numerical building blocks of the Rust crate, and the
special functions of scatrs.special.
Gauss–Legendre quadrature¶
Accuracy above 100 points.
f64rules above 100 points come from asymptotic expansions, within \(32\cdot2^{-52}\) (measured: 2.8 and 4.0 units of \(2^{-52}\) for nodes and weights) rather than the half ulp of the double-double rule rounded once.Double-double cost. Double-double rules use the \(O(n^2)\) Newton method at every \(n\). On an Apple M4 Pro that takes about 11 s at \(n\) = 20 000, and an extrapolated 8 minutes at the cap of \(2^{17}\) points. A call cannot be interrupted; compute a double-double rule once and reuse it.
Builds without FMA¶
On x86_64 the default target has no fused multiply-add, so every double-double operation calls
a software mul_add. That makes double-double code, Gauss–Legendre included, about ten times
slower. Build with RUSTFLAGS="-C target-cpu=native" where the CPU has FMA.
Wigner d-functions¶
Bounds. The accuracy bounds are measured, not proven. The tests validate them up to \(l = 2.5\cdot10^5\) for \((m, n) = (0, 0)\) and \((0, 2)\). For other pairs they validate them at sampled degrees up to \(l = 2000\), up to \(l = 3000\) in two cases with underflowing start values, and up to \(l = 4000\) only for \((35, 34)\) and \((0, 0)\) next to the poles. One-off measurements went to \(l\) = 20 000. See Wigner d-functions.
Second derivatives at the poles. With nested duals, second derivatives at \(x = 1\) are silently zero when \(\lvert m - n\rvert\) is 3 or 4, and at \(x = -1\) when \(\lvert m + n\rvert\) is 3 or 4. The true second derivative is infinite for 3, and finite but nonzero for 4.
Output size. One Python call returns at most
MAX_WIGNER_D_VALUES= \(2^{28}\) values (2 GiB of float64).
Double-double arithmetic¶
Trigonometric domain.
sin,cosandtanreturn NaN for \(\lvert x\rvert > 2^{50}\), wheref64still gives finite values. Kernels that take angles from users must check them first.Near multiples of \(\pi/2\). Beyond \(\pi/4\) the contract of
sinandcosis only \(2^{-100}\) absolute. The tests also check a relative bound with an absolute floor, \(2^{-100}\lvert f\rvert + \lvert k\rvert\cdot2^{-161}\) forf64arguments. So relative accuracy is lost only within about \(\lvert k\rvert\cdot2^{-60}\) of a nonzero multiple \(k\pi/2\). See DoubleDouble.Full precision. It needs magnitudes of at least \(2^{-969}\).
Platform-dependent last bits.
ln,cbrtandatan2(and functions built on them) start from the platform’sf64functions. Their last bits can differ between platforms, within the accuracy bounds, so bitwise golden tests across operating systems would fail.mul_addis not fused. See Notes for generic code.Speed.
exptakes about 200 ns andsinabout 170 ns.
Complex arithmetic¶
num_complex::Complex<T> uses textbook formulas, which are accurate only in norm, for
Complex<f64> as well as Complex<DoubleDouble>:
division overflows in
f64for components beyond about \(10^{154}\), and has no componentwise accuracy;lnnear \(z = 1\) loses relative accuracy.
For Complex<DoubleDouble> specifically:
sqrtnext to the negative real axis. It can return the wrong branch.sqrt(-9 + 1e-300i)gives \(\operatorname{Re} \approx -4.5\cdot10^{-33}\) instead of \(+1.7\cdot10^{-301}\).Division by tiny divisors. It loses precision for \(\lvert w\rvert \lesssim 10^{-146}\).
NaN where
f64is finite.exp,sin,cos,powcand related functions return NaN when a component exceeds \(2^{50}\), the double-double trigonometric domain.
The Mie kernels use their own complex reciprocal (the plain formula where it can neither overflow nor underflow, Smith’s algorithm elsewhere), validated against Arb, in place of num-complex’s.
Dual numbers¶
Roots of squares.
sqrt(p * p)at \(p = 0\) gives derivative 0, not \(\pm p'\). Useabsorhypot. See Strong zeros.Points where a function is not differentiable. These have fixed conventions. For example,
hypothas derivative 0 at the origin, whileatan2has NaN derivatives there. See Points where a function is not differentiable.Apple targets.
Dual<f64, N>sinandcostake their values from the fusedsincos. For about 1 in 13 000 arguments its sine is an ulp off a lonef64::sin. See sin and cos.Complex<Dual>values. Strict zero tests change some values ofComplex<Dual>functions from those ofComplex<f64>, by a few ulps or in special cases. See Comparisons and zero tests.libm accuracy. The tests of
f64derivatives assume each libm call is within 2 ulps. That has been measured only on macOS (arm64); the tests also run with glibc and the Windows C runtime, and weekly on Intel macOS. There Apple’ssinandcosreach 2.4 units of \(2^{-53}\) (1.1 on arm64, 1.0 with glibc on x86_64), so the test of the valuesDualtakes from them allows 3 units instead of 2 on that target only.Vectorisation. A loop over many
Duals is not vectorised across elements.