climate-indices 0.1.0

Pure-Rust numerical kernels for climate indices: SPI, SPEI, EDDI, PET, Palmer, fire weather, and flood
Documentation

climate-indices

Pure-Rust numerical kernels for climate and drought indices: the Standardized Precipitation Index (SPI), the Standardized Precipitation Evapotranspiration Index (SPEI), the Evaporative Demand Drought Index (EDDI), potential evapotranspiration (PET), the Palmer family, fire-weather indices, and flood indices.

The crate's only dependency is ndarray: no I/O, logging, or metadata layer. It provides kernels, not complete index workflows: the caller prepares calendars, selects the calibration period, validates physical ranges, and applies any aggregation and final index transformation.

Usage

[dependencies]
climate-indices = "0.1"
ndarray = "0.17"

Array inputs borrow ndarray views and array outputs own their data. A daily block has shape (days, cells), so one series and a grid use the same call:

use climate_indices::{ClimateError, flood::effective_precipitation};
use ndarray::array;

fn main() -> Result<(), ClimateError> {
    let grid_mm = array![[1.0, 10.0], [2.0, 20.0], [3.0, 30.0]];
    let effective = effective_precipitation(grid_mm.view(), 2)?;
    assert_eq!(effective.dim(), (3, 2));
    assert_eq!(effective[[1, 1]], 35.0);
    Ok(())
}

The crate documentation on docs.rs states the shape, calendar, unit, and error conventions for each kernel.

Examples

The crate ships runnable programs that build an index from plain arrays, with no file input and no orchestration crate:

cargo run --example spi    # SPI-3 from a monthly precipitation array
cargo run --example spei   # SPEI-6 from precipitation and PET arrays
cargo run --example pet    # Thornthwaite PET from monthly temperatures

Each example performs the caller-side steps the kernels document. SPI and SPEI prepare the calendar, accumulate the Timescale window, select the Calibration Period, estimate a discrete zero mass where the distribution has one, and apply the standard-normal transform; the PET example prepares temperatures, latitudes, and leap-year flags for a single kernel call.

Numerical reference

The kernels originate in the climate_indices project, whose reference implementations are the numerical parity oracle: the project's parity suite holds every kernel to rtol = atol = 1e-10 against them.

Versioning

The crate follows its own 0.x version track, independent of the climate_indices distribution. Before 1.0, a minor version can make breaking changes. ndarray is a public dependency, so an incompatible ndarray upgrade is a breaking release.

The minimum supported Rust version is 1.85.

Kernel benchmarks

Run the Criterion suite from the workspace root:

cargo bench -p climate-indices --bench kernels --locked
# Compile and check every fixture and kernel once, without collecting timings:
cargo bench -p climate-indices --bench kernels --locked -- --test
# Measure one kernel in both cell configurations:
cargo bench -p climate-indices --bench kernels --locked -- gamma_parameters

The suite needs only Rust. Criterion is a development dependency; its plotting and parallel-analysis features are disabled. Version 0.7 supports the crate's Rust 1.85 minimum. Cargo uses the optimized bench profile and the workspace's thin LTO and single codegen unit. No custom CPU flags are required.

benches/kernels.rs generates small deterministic fixtures without files, downloads, or a random-number dependency. Its base sample is ((step * 37 + cell * 17 + 11) % 101) / 100.0. Monthly cases use 40 years (1980–2019), 12 months, and either 1 or 32 cells. Rain includes zeros; the log-logistic fit uses signed synthetic water-balance values. Fits use all 40 years. Thornthwaite uses positive temperatures, 40° latitude, and Gregorian leap flags. EDDI ranks only January's PET against its 40-year sample, without leading pads. Palmer receives rain, PET, and a five-inch available water capacity in inches. Daily cases use 1,830 consecutive positions and either 1 or 32 cells. FFMC starts at 85, records all history, and has valid weather with no gaps or seasonal mask. Effective precipitation uses a 30-day window, whose first 29 outputs are missing. These synthetic fixtures are measurement inputs, not scientific reference data.

Fixture generation, fit parameters for the probability benchmark, unit conversions, and output assertions run outside the timed loops. Each timed iteration calls one kernel with borrowed views and consumes its output through std::hint::black_box. Kernel allocations and output destruction are included. Each recurrence starts from the same initial state. The smoke command checks fit validity, probability ranges, PET, water-balance and FFMC outputs, and effective-precipitation padding.

This is a representative subset, not complete workflow coverage: Gamma fit and probabilities, log-logistic fit, EDDI ranking, Thornthwaite PET, Palmer water balance, FFMC, and effective precipitation. It excludes other PET, distribution, Palmer, fire, and flood kernels, final index transformations, I/O, and parallel execution. It does not measure complete SPI, SPEI, EDDI, or PDSI workflows.

Representative measurements

Measured on 2026-10-19 with an Apple M5 (10 physical/logical cores), 32 GiB RAM, macOS 27.0.1 arm64, Rust/Cargo 1.99.0, and Criterion 0.7.0. The suite calls the kernels serially. The command above used Criterion's defaults: a three-second warm-up, five-second measurement target, and 100 samples per case. The recorded run added -- --noplot; it used no custom RUSTFLAGS.

Times and throughput below are Criterion's central estimates, not minimum times. Throughput counts millions of input time-cell values per second (Melem/s), not output arrays or arithmetic operations. EDDI counts 40 values per cell; the other monthly kernels count 480. Daily kernels count 1,830, including window padding.

Kernel 1 cell, µs/call 1 cell, Melem/s 32 cells, µs/call 32 cells, Melem/s
gamma_parameters 1.049 457.46 76.779 200.05
gamma_probabilities 31.092 15.44 2425.5 6.33
loglogistic_parameters 3.929 122.17 155.45 98.81
thornthwaite 9.912 48.43 238.77 64.33
tukey_probabilities 0.464 86.27 19.538 65.52
water_balance 2.588 185.49 417.12 36.82
ffmc 79.187 23.11 2349.0 24.93
effective_precipitation 6.506 281.28 282.19 207.52

The raw output preserves the 95% confidence intervals and outlier counts. The 32-cell Gamma cases have wide intervals; repeat measurements before making performance decisions. Criterion also writes local estimates under target/criterion/. These machine-specific results are not speedup claims or CI thresholds. Re-running reproduces the inputs and protocol, not identical elapsed times.

License

BSD-3-Clause. The LICENSE file includes the third-party notices for the ported Cephes and NumPy routines.