rustmatrix 2.1.1

Rust-backed T-matrix scattering for nonspherical particles (port of pytmatrix)
Documentation
# Conventions

A one-stop reference for the sign, unit, geometry, and naming
choices `rustmatrix` makes. These are the places where you are most
likely to get off by a factor / a sign / a degree compared with the
paper you are trying to reproduce.

## Units

| Quantity | Units in rustmatrix |
|---|---|
| Length (drop diameter, radius, wavelength) | **mm** |
| Distance (range, path) | **km** (for $K_{dp}$, $A_i$) |
| Velocity (terminal, Doppler, wind) | **m s⁻¹** |
| Reflectivity, linear | **mm⁶ m⁻³** |
| Reflectivity, log | **dBZ** — $10 \log_{10}$ of the linear value |
| Cross sections | **mm²** |
| $K_{dp}$ | **° km⁻¹** |
| $A_h, A_v$ | **dB km⁻¹** |
| Temperature | **K** (except in fall-speed presets taking $T$) |
| Pressure | **Pa** |
| Angles in API | **radians** for geometry tuples, **degrees** for canting σ |

The dBZ convention is the meteorological one (i.e. the linear value
in the log is always mm⁶ m⁻³, not m⁶ m⁻³).

## Polarization naming

* `h` — horizontal linear polarization.
* `v` — vertical linear polarization.
* Scatterer defaults are reported at the horizontal polarization
  (`h_pol=True`) throughout `rustmatrix.radar`.
* LDR is the co-to-cross ratio, $|S_{hv}|^2 / |S_{hh}|^2$, in dB.

## Axis ratios

Drop-shape relations in the literature and in `rustmatrix.tmatrix_aux`
report **$h/v$** (horizontal over vertical) — ≥ 1 for oblate drops.

The `Scatterer(axis_ratio=...)` argument expects the inverse, **$v/h$**,
because that is what the underlying Mishchenko FORTRAN kernel
consumes. Rain snippets therefore always look like

```python
axis_ratio=1.0 / dsr_thurai_2007(D)
```

This is the single most common foot-gun in porting a `pytmatrix`
script. The convention matches `pytmatrix` for drop-in compatibility.

## Geometries

Pre-built geometry tuples live in `rustmatrix.tmatrix_aux`:

| Name | Use |
|---|---|
| `geom_horiz_back` | horizontally-pointing antenna, 180° back-scatter — drives $Z_h, Z_{dr}, \rho_{hv}, \delta_{hv}$ |
| `geom_horiz_forw` | horizontally-pointing antenna, 0° forward-scatter — drives $K_{dp}, A_i$ |
| `geom_vert_back`  | vertically-pointing antenna, back-scatter — profiler / cloud-radar spectra |

Call `s.set_geometry(...)` to switch. The underlying T-matrix is
cached on the `Scatterer`, so the switch is O(1).

## Doppler sign

Positive Doppler velocity = fall direction = toward a *down*-pointing
radar. This is the convention used in
{cite}`Kollias2002,Zhu2023,BillaultRoux2023`. For an up-looking
profiler, flip the sign of `w` and of `v_bins` passed to
`SpectralIntegrator` — the kernels are even in $v$ so this is a pure
sign flip, not a re-compute.

## Canting angles

* Mean canting angle is measured from the vertical (so a horizontally-
  aligned oblate drop has mean canting 0°).
* `canting_std` is in **degrees**, applied as a zero-mean Gaussian PDF
  around the mean.
* Full random orientation is reached in the limit of large
  `canting_std`; `rustmatrix` also provides closed-form random-
  orientation averaging if that is what you want.

## Refractive index

`rustmatrix.refractive` tables are indexed by wavelength in mm:

```python
from rustmatrix.refractive import m_w_0C, m_w_10C, m_w_20C, m_i
from rustmatrix.tmatrix_aux import wl_C
m_rain = m_w_10C[wl_C]
```

`m_w_*C` are liquid water at 0 / 10 / 20 °C; `m_i` is solid ice.
Refractive index follows the physics convention $m = n + ik$, $k > 0$
for an absorbing medium.

## Wavelength constants

`tmatrix_aux.wl_S, wl_C, wl_X, wl_Ku, wl_Ka, wl_W` — the standard
radar-band wavelengths in mm. Use them as dictionary keys into the
refractive-index and $|K_w|^2$ tables:

```python
from rustmatrix.tmatrix_aux import wl_W, K_w_sqr
kw = K_w_sqr[wl_W]
```

## Scatterer caching

The `Scatterer` caches:

1. the T-matrix itself (invalidated by changes to size, shape,
   refractive index, or wavelength),
2. the amplitude/phase matrices at the current geometry.

This means iterating over PSDs or canting parameters is cheap once
the T-matrix is computed. It also means **mutating `s.m`, `s.radius`,
or `s.wavelength` after the first call silently requires a
re-compute** — `rustmatrix` handles the invalidation for you, but if
you are benchmarking, change one parameter, re-set geometry, call
once to warm the cache.

## Drop-in compatibility with pytmatrix

The `Scatterer`, `radar.*`, and `psd.*` public APIs are intentionally
1:1 with `pytmatrix` where the physics matches. A script written for
`pytmatrix` typically ports to `rustmatrix` by changing only the
import. Places where we deliberately diverge:

* `HydroMix` and `SpectralIntegrator` — new capabilities, no
  `pytmatrix` analogue.
* Full-random-orientation averaging is exact in `rustmatrix`
  (closed form); `pytmatrix` uses numerical quadrature. Both agree to
  floating-point precision on the parity test suite.
* Some `pytmatrix` private attributes are not preserved — only the
  documented public surface is stable across the port.

## References

```{bibliography}
:filter: docname in docnames
```