Benchmarks and verification
Gaussian random-intercept ML verification record
The executed comparison fits identical observations in R lme4 ML, Python statsmodels MixedLM ML and PHP fit_mixed_intercept. The saved report records versions, full outputs, Python warnings/backend, covariance convention and differences from R.
Six saved R fixtures cover simulated schools with correlated predictors, a single predictor, no fixed intercept, intercept only, a singleton group and an exact zero-group-variance boundary. Three-way verification adds missingness in predictors, outcomes and group IDs. Independent Rust tests also check a balanced intercept-only sample with analytically known variances and shrinkage. The deterministic tests run without R/Python installed.
Four additional published datasets—sleepstudy, Orthodont, Machines and Hsb82— are checked against both lme4 and nlme saved ML outputs in Rust and PHP. The published-data notebook executes fresh fits in both R packages, statsmodels and PHP and checks every row. See source provenance, tests/fixtures/lmm-public-r.json and the live report. Published-data tolerances retain 1e-7 absolute + 1e-6 relative for ordinary quantities, use 1e-6 absolute + 1e-6 relative for observation-length outputs, and 1e-7 absolute + 1e-10 relative for likelihood/AIC. The notebook documents the measured R optimizer precision limit for near-zero Hsb82 residuals. A separate native-versus-statsmodels check keeps the strict original uniform tolerance.
All fixed-effect inference, covariance, population/conditional fitted values, both residual definitions, group effects, variances, ICC, marginal likelihood and AIC are compared at 1e-7 + 1e-6 × abs(reference). Labels/order and missing positions are exact. Python uses statsmodels estimates with plug-in GLS covariance to match lme4; its native joint-Hessian covariance is a different convention. The boundary fixture’s Python reference is explicitly the NumPy OLS ML limit, not a statsmodels mixed optimizer result.
PHP tests check integer/string group identity, null group deletion, invalid float/boolean labels before deletion, missing warnings and strict rejection. Rust tests check units, row/group order, invalid shapes, unidentified grouping, rank deficiency, perfect fits and unsupported zero-residual-variance limits. An independent observation-level GLS test checks the cached QR reduction at several variance ratios, with correlated predictors and large outcome offsets; it verifies both optimized and arbitrary-coefficient residual norms. The profile search has a finite log-ratio range/grid and is not an exhaustive global optimization proof. The first model has no REML, random slopes, multiple grouping factors, finite-sample df adjustments or variance-component tests. Normal inference with few groups can be unreliable.
export PHP=/opt/homebrew/bin/php
export PHP_CONFIG=/opt/homebrew/bin/php-config
cargo test --workspace --locked
cargo build -p phit --locked
php -n -d "extension=$PWD/target/debug/libphit.dylib" tests/lmm.php
.context/notebooks-venv/bin/python docs/verifications/compare_lmm.py
QUARTO_PYTHON="$PWD/.context/notebooks-venv/bin/python" quarto render docs/notebooks/lmm-verification.qmdFor the published comparisons, run the same command with --public. Regenerate public expectations from the pinned snapshots with .context/notebooks-venv/bin/python tests/generate-lmm-public-fixtures.py; add --export only when intentionally refreshing upstream data.
Use matching PHP tools and .so on Linux. Regenerate intentionally with Rscript tests/generate-lmm-fixtures.R; lme4 is a development dependency. See the contract, math and benchmark. Linux/PHP-FPM and broader mixed-model structures are not established by this evidence.
From phpstats/phit@76b22f0.