glmm 0.1.0

Standalone f64 GLMM fit kernels (OLS, GLM, LMM, GLMM) in pure Rust on faer — the parity-pinned numerics from the MCPower engine.
Documentation

glmm

CI crates.io docs.rs License: GPL-3.0-or-later MSRV

Standalone f64 GLM(M) fit kernels — OLS → GLM → LMM → GLMM — in pure Rust on faer.

Fits fixed-effect and mixed (random-intercept/random-slope) models for Gaussian, Binomial (logit/probit), Poisson, Gamma, and Negative-Binomial outcomes, validated against R/lme4 and Julia/MixedModels.jl goldens.

New to the crate? Start with TUTORIAL-RUST.md — a single-page, three-layer walkthrough (cold fit → warm fit → advanced loop) plus a short section on parsing an R-style formula string instead of building inputs by hand. A Python package is also available — see TUTORIAL-PYTHON.md.

One crate, one fit

A single entry point covers the whole linear-regression family. fit reads ModelSpec and routes on family × random effects × weights:

flowchart LR
    F["fit(x, y, ModelSpec)"] --> RE{"re?"}
    RE -- "None" --> FAM1{"family?"}
    FAM1 -- "Gaussian" --> OLS["OLS - WLS with weights"]
    FAM1 -- "other" --> GLM["GLM - IRLS"]
    RE -- "Some" --> FAM2{"family?"}
    FAM2 -- "Gaussian" --> LMM["LMM - profiled REML"]
    FAM2 -- "other" --> GLMM["GLMM - PIRLS, Laplace/AGQ"]

This is the pitch view. Every branch point, solver path, and tuning knob is traced to code in the full algorithm map: documentation/algorithms.md, with the LMM and GLMM legs detailed in documentation/algorithms-lmm.md and documentation/algorithms-glmm.md.

Documentation

File Purpose
documentation/TUTORIAL-RUST.md Three-layer Rust walkthrough: cold fit → warm fit → advanced loop, plus the formula frontend
documentation/TUTORIAL-PYTHON.md The Python package (glmm) walkthrough
documentation/supported_families.md Family × link support matrix, canonical-link notes, dispersion conventions
documentation/algorithms.md Algorithm map entry point: full dispatch graph, knob index, OLS/GLM paths
documentation/algorithms-lmm.md LMM: θ-Cholesky, profiled REML, closed-form shortcut, BOBYQA, boundary handling
documentation/algorithms-glmm.md GLMM: PIRLS, Laplace vs AGQ, dense vs sparse Z, NB outer loop, warm starts

Scope and stability (0.1.x)

The semver-covered surface is fit_cold/fit_warm + ModelSpec + GroupIds.

Model Fixed-only (re: None) Mixed (re: Some)
Gaussian OLS LMM — dense, or sparse-Z when an extra grouping carries a random slope
Binomial (logit/probit) GLM GLMM — dense, or sparse-Z over-envelope
Poisson (log) GLM GLMM — dense, or sparse-Z over-envelope
Gamma (log/inverse) GLM GLMM — dense, or sparse-Z over-envelope
Negative-Binomial (log) GLM GLMM — dense, or sparse-Z over-envelope

Every wired family fits through both routes — there is no reachable panic for falling outside the dense solver's envelope (too many extra groupings, or an extra grouping too wide); classification just routes to the sparse-Z solver instead. See TUTORIAL-RUST.md and documentation/algorithms-glmm.md for the dense/sparse routing envelope.

The loop_advanced cargo feature (off by default) exposes an unstable scratch-explicit hot-path surface for warm-start callers like MCPower's simulation loop — no semver guarantees; do not depend on it outside a pinned revision.

The parallel cargo feature (off by default, experimental) enables in-fit parallelism — the AGQ cluster loop and the FD-Hessian SE grid — via rayon, and is additionally gated at runtime by FitOptions::parallel_inner (also off by default): both the feature and the flag must be on for any thread to spawn. Parallel results are bit-identical to serial ones, but the kernels are new and their performance envelope isn't characterized yet — treat as opt-in only. A no-op on wasm32 (compile-time excluded, rayon never pulled in).

Quick example

use glmm::{fit_cold, Family, FitOptions, GroupIds, ModelSpec, ReStructure, Sizing};

// y ~ x + (1 | group), Gaussian — a random-intercept LMM.
let model = ModelSpec {
    family: Family::Gaussian,
    re: Some(ReStructure {
        sizing: Sizing::FixedClusters { n_clusters: 6 },
        slopes: vec![],
        extra_groupings: vec![],
    }),
};
let ids = GroupIds { primary: vec![0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5], extra: vec![] };
// target_indices = which coefficients get a standard error; here, both.
let opts = FitOptions { target_indices: vec![0, 1], ..Default::default() };

let fit = fit_cold(&x, &y, n, p, &model, &ids, &opts);
assert!(fit.converged);

See TUTORIAL-RUST.md for the full walkthrough: warm starts, the advanced hot-loop surface, and building x/ModelSpec/GroupIds from a formula string with glmm::formula (the formula feature, on by default) instead of by hand.

Design

Property Detail
No unsafe zero unsafe (workspace baseline unsafe_code = "warn")
Deterministic no RNG, no global state, no I/O in the fit path
Linear algebra faer 0.24, pinned
MSRV Rust 1.85 (floor set by the bobyqa dep)

Origin

glmm was carved out of MCPower's simulation engine — the numerics here are the same parity-pinned kernels that power MCPower's Monte Carlo fits, split out so they're usable standalone. The loop_advanced feature above exists specifically to serve MCPower's warm-start hot loop as a consumer of this crate.

License

GPL-3.0-or-later (coupled to the GPL-3 MCPower flagship).


Paweł Lenartowicz — Freestyler Scientist · GitHub · ORCID