u-numflow 0.6.4

Foundational mathematical primitives: statistics, probability distributions, random sampling, and collections.
Documentation

u-numflow

Domain-agnostic mathematical primitives in Rust

Crates.io docs.rs CI License

Overview

u-numflow provides foundational mathematical, statistical, and probabilistic building blocks. Entirely domain-agnostic with no external dependencies beyond rand.

Modules

Module Description
stats Descriptive statistics (mean, variance, skewness, kurtosis) with Welford's online algorithm and Neumaier summation
distributions Probability distributions: Uniform, Triangular, PERT, Normal, LogNormal
special Special functions: normal CDF and tail-precise survival function (and their inverses), t/F/chi² CDF and quantiles, the noncentral t CDF (Lenth AS 243 — the distribution power is computed from), regularized incomplete beta/gamma, erf
transforms Data transformations: Box-Cox (λ via MLE golden-section search), inverse Box-Cox
fourier Discrete Fourier transform of any length (radix-2 for powers of two, Bluestein otherwise): fft, ifft, rfft, Complex
matrix Dense matrix operations: determinant, inverse, Cholesky decomposition, Jacobi eigenvalue decomposition
random Seeded RNG, Fisher-Yates shuffle, weighted sampling, random subset selection
collections Specialized data structures: Union-Find with path compression and union-by-rank

Design Philosophy

  • Numerical stability first — Welford's algorithm for variance, Neumaier summation for accumulation
  • Reproducibility — Seeded RNG support for deterministic experiments
  • Property-based testing — Mathematical invariants verified via proptest

Quick Start

[dependencies]
u-numflow = "0.2"
use u_numflow::distributions::Pert;
use u_numflow::random::{create_rng, shuffle};
use u_numflow::stats::WelfordAccumulator;

// Online statistics with numerical stability (Welford)
let mut stats = WelfordAccumulator::new();
for x in [1.0, 2.0, 3.0, 4.0, 5.0] {
    stats.update(x);
}
assert_eq!(stats.mean(), Some(3.0));

// PERT distribution: moments and quantiles; sample by inverting a uniform draw
let pert = Pert::new(1.0, 4.0, 7.0).unwrap();
assert_eq!(pert.mean(), 4.0);
let p90 = pert.quantile(0.9).unwrap();
assert!(p90 > 4.0 && p90 < 7.0);

// Seeded shuffling for reproducibility
let mut rng = create_rng(42);
let mut items = vec![1, 2, 3, 4, 5];
shuffle(&mut items, &mut rng);

// Box-Cox transformation (non-normal data normalization)
use u_numflow::transforms::{box_cox, estimate_lambda};
let data = [1.0, 2.0, 4.0, 8.0, 16.0];
let fit = estimate_lambda(&data, -2.0, 2.0).unwrap(); // MLE via golden-section
let transformed = box_cox(&data, fit.lambda).unwrap();
assert_eq!(transformed.len(), data.len());

// Discrete Fourier transform of any length
use u_numflow::fourier::rfft;
let signal: Vec<f64> = (0..30).map(|j| (2.0 * std::f64::consts::PI * 3.0 * j as f64 / 30.0).sin()).collect();
let spectrum = rfft(&signal);           // 30 complex bins; bin 3 carries the energy
assert!(spectrum[3].norm() > 14.0);

Build & Test

cargo build
cargo test

Dependencies

  • rand 0.9 — Random number generation
  • proptest 1.4 — Property-based testing (dev only)

License

MIT License — see LICENSE.

npm (WebAssembly)

npm install @iyulab/u-numflow

The package resolves per environment via a conditional exports map:

Environment Entry
Bundlers (webpack, Vite, …) ESM + WebAssembly ESM-integration (default condition)
Node.js — require(), ESM import, CJS TS runners (tsx, ts-node) CJS glue loading the wasm from the filesystem (node condition) — no loader hooks or flags

Exported functions: mean, std_dev, variance, normal_cdf, normal_sf (upper tail P(Z > x), computed directly so tail probabilities keep ~15 significant digits), box_cox, estimate_lambda (returns { lambda, at_bound } — at_bound is true when the likelihood was still rising at an end of the search range, so lambda is that limit), and rfft(data) -> Float64Array — the DFT of a real sequence of any length, interleaved as [re0, im0, re1, im1, …] (bins k and n − k are conjugates, so 0..=n/2 describes the spectrum).

Distribution functions for critical values and p-values — each returns a number and throws (a message naming the argument) when an argument is outside the domain, rather than returning NaN:

Function Returns
inverse_normal_cdf(p) z with P(Z ≤ z) = p
t_distribution_cdf(t, df) / t_distribution_quantile(p, df) Student's t; a two-sided critical value at level α is t_distribution_quantile(1 − α/2, df)
f_distribution_cdf(x, df1, df2) / f_distribution_quantile(p, df1, df2) F
chi_squared_cdf(x, k) / chi_squared_quantile(p, k) χ²

p must lie strictly between 0 and 1 and every degrees-of-freedom argument must be a finite number > 0 (fractional values are allowed).

const { t_distribution_quantile } = require("@iyulab/u-numflow");
t_distribution_quantile(0.975, 10); // 2.2281…

TypeScript

Every exported function declares its parameter and return types, and the declarations are generated from the same structs the binding reads and serialises, so they cannot drift from what it actually accepts and returns:

export function estimate_lambda(data: Float64Array, lambda_min: number, lambda_max: number): LambdaEstimateDto;

An absent optional value is declared T | undefined, which is what the binding sends. Nothing needs an as cast -- and a wrong assumption about a result's shape is a compile error rather than something that fails at run time.

Every parameter is a number or a Float64Array, so there is nothing to declare beyond what wasm-bindgen already does; the publishing workflow keeps it that way. The binding still validates every input at the boundary, and a rejected one says what was wrong.

Related