Expand description
Pure-Rust numerical kernels for climate_indices.
The public modules expose numerical kernels, not a complete SPI/SPEI workflow. Callers prepare calendars, select the Calibration Period, validate physical ranges, and apply any required aggregation and final index transformation. Kernels check the structural constraints documented on each function. This crate has no Python, I/O, logging, or metadata dependency.
§Arrays and compatibility
Array inputs borrow ndarray views; array outputs own their data.
Other kernels accept scalars or slices and return their documented result types.
Strided views are supported unless a function documents a restriction.
ndarray is a public dependency, not a re-export: callers use a compatible
ndarray dependency.
An incompatible ndarray upgrade requires a breaking crate release.
The crate has no feature flags and no optional runtime dependency.
§Shapes, calendars, and units
All array indices are zero-based. A daily block has shape (days, cells).
One series uses one cell; a grid uses one column per independent cell.
Distribution fits use (years, columns), with one column per calendar step
and cell. A caller can use column = step * cells + cell consistently for
values and fit parameters. Monthly records have 12 calendar steps per year;
the prepared all-leap daily layout has 366, including a February 29 position
in every year. Kernels do not infer calendar dates or insert missing days.
Precipitation and PET kernels use mm, temperatures use degrees C, and daily
window lengths count calendar days. PET latitude inputs use radians in
eto and degrees in pm_eto. Palmer water-balance inputs instead use
inches. Function documentation specifies other units and preconditions.
NaN handling is kernel-specific; NaN is not a substitute for shape validation.
§One series and a grid
flood::effective_precipitation accepts both layouts without a wrapper or
a copy of the input. Its output has the same shape as the input.
use climate_indices::{ClimateError, flood::effective_precipitation};
use ndarray::{Axis, array};
let series_mm = array![1.0, 2.0, 3.0];
let series = effective_precipitation(series_mm.view().insert_axis(Axis(1)), 2)?;
assert_eq!(series.dim(), (3, 1));
assert!(series[[0, 0]].is_nan());
assert_eq!(series[[1, 0]], 3.5);
let grid_mm = array![[1.0, 10.0], [2.0, 20.0], [3.0, 30.0]];
let grid = effective_precipitation(grid_mm.view(), 2)?;
assert_eq!(grid.dim(), (3, 2));
assert_eq!(grid[[1, 0]], series[[1, 0]]);
assert_eq!(grid[[1, 1]], 35.0);§Calendar-column fitting
gamma::gamma_parameters fits each column independently. This small
synthetic sample illustrates the layout, not a sufficient Calibration Period.
Gamma shape is dimensionless; gamma scale has the input’s units (here mm).
The result below is a probability, not SPI.
use climate_indices::{ClimateError, gamma::{gamma_parameters, gamma_probabilities}};
use ndarray::{Array1, Array2};
let (years, steps, cells) = (3, 12, 2);
let rain_mm = Array2::from_shape_fn((years, steps * cells), |(year, column)| {
10.0 * (year + column + 1) as f64
});
let (shape, scale_mm) = gamma_parameters(rain_mm.view());
let probability_of_zero = Array1::zeros(steps * cells);
let probabilities = gamma_probabilities(
rain_mm.view(), shape.view(), scale_mm.view(), probability_of_zero.view(),
)?;
let (year, step, cell) = (1, 5, 1);
assert!((0.0..=1.0).contains(&probabilities[[year, step * cells + cell]]));§Errors and numerical behavior
Match ClimateError variants and fields, not diagnostic text. Public enums
are non-exhaustive, so downstream matches need a wildcard arm. Display text
and diagnostic strings are not stable API and carry no Python-message promise.
climate-py owns Python exception translation. The Python implementations
remain the numerical parity oracles. Each public kernel documents its numerical
contract; the measured evidence below distinguishes workflow and scalar errors.
No kernel’s operation order changes with this surface.
§Measured accuracy
Measured on Darwin arm64 (CPython 3.14.7), NumPy 2.4.2, SciPy 1.17.0. The values describe this deterministic corpus, not universal error bounds or independent scientific validation. No algorithm, physical-range validation, or calendar preparation changes in this contract.
§Workflow measurements
Each row compares a complete workflow against the Python/SciPy reference with native dispatch disabled. The harness verifies that the listed Rust kernels actually ran. A workflow error is not an isolated error bound for each kernel it uses. The numerical contracts on each function identify the applicable rows.
The corpus comes from tests/parity_registry.py::SAMPLES, with fixed seeds 20261009, 20261010, and 20261011.
It contains 35 monthly years, five 365-day daily years, and a 35-year monthly Spatial Block with three cells.
The samples include zeros, NaNs, and extreme precipitation; entry-specific derivations prepare each kernel’s inputs.
Palmer replaces gaps and clips precipitation to [5, 300] in the inch-based water-balance inputs.
The monthly record starts in 1981; its Calibration Period includes 1983 through the final record year, 2015.
Only flood_edi and flood_flood_index prepare an all-leap calendar; flood_pe and flood_api retain the 365-day inputs.
The registry records the exact derivations and parameters.
| Entry | Rust kernels reached | Max absolute | Max relative |
|---|---|---|---|
eddi | hastings_inverse_normal, tukey_probabilities | 0.000000e+00 | 0.000000e+00 |
eddi_spatial_block | hastings_inverse_normal, tukey_probabilities | 0.000000e+00 | 0.000000e+00 |
fire_drought_code | drought_code | 0.000000e+00 | 0.000000e+00 |
fire_duff_moisture_code | duff_moisture_code | 0.000000e+00 | 0.000000e+00 |
fire_ffmc | ffmc | 0.000000e+00 | 0.000000e+00 |
fire_kbdi | kbdi | 0.000000e+00 | 0.000000e+00 |
fit_diagnostics | gamma_parameters | 0.000000e+00 | 0.000000e+00 |
flood_api | antecedent_precipitation_index | no finite comparison | no finite comparison |
flood_edi | edi, effective_precipitation | 8.526513e-14 | 7.882583e-15 |
flood_flood_index | effective_precipitation, flood_index | 2.220446e-16 | 1.096055e-15 |
flood_pe | effective_precipitation | 4.547474e-13 | 2.780533e-16 |
hargreaves | hargreaves | 4.440892e-16 | 1.974933e-16 |
palmer_pdsi | palmer_k_prime, palmer_pdi, palmer_raw_zindex, palmer_water_balance | 0.000000e+00 | 0.000000e+00 |
palmer_scpdsi | palmer_k_prime, palmer_raw_zindex, palmer_water_balance, palmer_wells, scpdsi_duration_factors | 0.000000e+00 | 0.000000e+00 |
pci | pci | 0.000000e+00 | 0.000000e+00 |
pearson_ks_statistics | pearson_ks_statistics | 3.330669e-16 | 1.829831e-15 |
penman_monteith | fao56_eto | 8.881784e-16 | 4.336616e-16 |
percentage_of_normal | pnp_normals, pnp_percentages | 0.000000e+00 | 0.000000e+00 |
pm_eto_intermediates | pm_eto | 0.000000e+00 | 0.000000e+00 |
spei_gamma | gamma_parameters, gamma_probabilities, norm_ppf | 2.220446e-16 | 3.456693e-16 |
spei_loglogistic | loglogistic_cdf, loglogistic_parameters | 0.000000e+00 | 0.000000e+00 |
spi_gamma | gamma_parameters, gamma_probabilities, norm_ppf | 1.998401e-15 | 4.764927e-14 |
spi_gamma_mean_zero | gamma_parameters, gamma_probabilities, norm_ppf | 1.998401e-15 | 4.764927e-14 |
spi_gamma_spatial_block | gamma_parameters, gamma_probabilities, norm_ppf | 7.105427e-15 | 1.867507e-13 |
spi_pearson | pearson_cdf, pearson_ks_statistics, pearson_parameters | 8.604228e-15 | 6.821559e-14 |
thornthwaite | thornthwaite | 2.842171e-14 | 2.207812e-16 |
These entries have no finite comparison on this corpus: flood_api.
Those rows record only matching NaN positions or signed infinities, not a numeric error.
§Direct special-function measurements
crates/climate-indices/examples/special_accuracy.rs prints scalar results for direct comparison with SciPy.
Gamma probes cross 14 shape values with 14 argument values from zero through 1e6, infinity, and NaN.
Quantile probes include 1e-300, central probabilities, both endpoints, and invalid inputs.
Normal-CDF probes include both tails through +/-40, zero, infinities, and NaN.
These rows measure the named scalar function, without an index workflow.
The corpus has 427 probes: 196 per gamma function, 12 per quantile function, and 11 normal-CDF probes.
The generator rejects missing, duplicated, or changed probe coordinates, including non-finite inputs.
| Entry | Max absolute | Max relative |
|---|---|---|
special_igam | 3.330669e-16 | 6.960316e-15 |
special_igamc | 1.110223e-16 | 3.704547e-15 |
special_ndtr | 0.000000e+00 | 0.000000e+00 |
special_ndtri | 0.000000e+00 | 0.000000e+00 |
special_norm_ppf | 0.000000e+00 | 0.000000e+00 |
§Interpretation and reproduction
Absolute error is abs(rust - reference); relative error divides that error by abs(reference) for nonzero references.
Zero references contribute only to absolute error. Matching NaNs and matching signed infinities do not contribute to maxima.
The tests check NaN positions and infinity signs separately through the parity comparator.
Every comparison must satisfy rtol = atol = 1e-10; those tolerances are acceptance criteria, not measured errors.
A reported relative error can exceed the relative tolerance when the absolute tolerance governs a value near zero.
State-plumbing helpers copy or index values and have no independent arithmetic accuracy claim.
Rust-only verification coverage and independent reference fixtures remain separate from these workflow measurements.
From the repository root, build the extension and regenerate this page:
uv run maturin develop --release
uv run python scripts/native_parity_maxima.py --write-contractThe second command also runs cargo run -p climate-indices --example special_accuracy --quiet.
Run uv run pytest tests/test_native_parity_registry.py to exercise the same deterministic workflow comparisons and property draws.
Cross-platform differences can change the maxima. Regenerate the page only after reviewing those differences.
Modules§
- eddi
- EDDI kernels: the empirical rank count and Tukey plotting position, and the Hastings inverse-normal approximation.
- eto
- Thornthwaite and Hargreaves PET kernels, mirroring
climate_indices.eto. - fire
- Fire-weather kernels: the KBDI and CFFWIS moisture-code daily recursions.
- flood
- Flood-family kernels: effective precipitation, EDI, the Flood Index, and the Antecedent Precipitation Index recurrence.
- gamma
- Gamma-distribution kernels behind SPI (and SPEI or the standardized index when fitted to gamma).
- lmoments
- Sample L-moments, the input to the Pearson Type III and generalized logistic fits.
- loglogistic
- Generalized logistic (log-logistic, GLO) kernels behind SPEI’s reference distribution.
- palmer
- Palmer-family water-balance kernel.
- palmer_
pdi - NCEI
pdi.f-lineage Palmer spell recursion behind standard PDSI, PHDI, and PMDI. - palmer_
wells - Wells-lineage Palmer recursion behind self-calibrating PDSI.
- palmer_
zindex - Palmer CAFEC precipitation and Z-index kernels.
- pci
- Precipitation Concentration Index (PCI) of one year of daily rainfall: the year’s monthly totals and their concentration ratio.
- pearson
- Pearson Type III kernels behind SPI and SPEI when fitted to Pearson Type III.
- pm_eto
- Penman-Monteith (FAO-56) PET kernels, mirroring
climate_indices.pm_eto. - pnp
- Percentage-of-normal precipitation (PNP): the calibration normal of each calendar step, and the ratio of every scaled total to its step’s normal.
- recurrence
- The daily-recurrence engine behind the fire and flood kernels.
- self_
calibration - Self-calibrating PDSI duration-factor fit.
- special
- Special functions ported from the Cephes library as SciPy 1.17.0 ships it.
Enums§
- Climate
Error - A structural input error or a numerical failure reported by a kernel.
Constants§
- VERSION
- Version of this crate, re-exported by the Python extension as
__version__.