Benchmarks and verification
Verification
CmdStanPHP is checked three ways: unit tests without CmdStan, integration tests that compile and run real models, and compatibility tests that run CmdStanR, posterior, loo, and bayesplot in R on the same inputs and compare the results. Contributing explains how to run them.
What CI tests
GitHub Actions runs these on Linux (Ubuntu) for every push to main and every pull request:
- Unit tests on PHP 8.2, 8.3, 8.4, and 8.5.
- Integration tests on PHP 8.4 with CmdStan 2.37.0 and 2.40.0, built by this package’s installer, with FFI and the phit extension loaded. Every figure kind is rendered to SVG, and one to PNG, with vl-convert 1.9.0.
- Compatibility tests on the CmdStan 2.40.0 job, against CmdStanR 1.0 from commit
0a54123of its v1.0 branch (version 0.99.0, a release candidate), and the posterior, loo, and bayesplot packages from stan-dev’s R-universe and CRAN.
The integration and compatibility jobs fail on skipped tests. macOS, native Windows, MPI clusters, and OpenCL hardware are not tested in CI. The integration suite has also been run on macOS (Apple silicon, PHP 8.4, CmdStan 2.40.0) during development, most recently on 2026-10-31.
Unit tests
The unit suite does not need CmdStan. Its moment-matching tests need the phit extension and skip without it. It verifies JSON shapes and invalid data, CSV comments/warmup/special numbers, draw layouts, summaries, diagnostics, serialization, option validation, process argument isolation, pipe draining, timeouts, cancellation, and bounded execution. It uses real PHP subprocesses for process behavior. Data frame interoperability is tested with stand-in tables and a stand-in Phibble\DataFrame, so phibble is not needed. Without phit, StanData::fromFormula() is tested with a stand-in Phit\model_matrix(). With it, the tests cover random intercepts and slopes, factor predictors and responses, and dropped rows against the real function.
Integration tests
The integration suite compiles the bundled models and invokes real CmdStan methods. It compares Bernoulli posterior estimates with their analytic mean, checks R-hat and ESS, verifies deterministic seeds with parallel chains, confirms modes and draw counts, and exercises generated quantities, fixed parameters, syntax/formatting, gradient checking, log density, input errors, metrics, and auxiliary files. It fits a hierarchical regression built with StanData::fromColumns(), runs a model on table data, and, when phit is loaded, fits score ~ study_hours + (1 | school) built with StanData::fromFormula() on phit’s schools data and compares the posterior means with Phit\fit_lmer(). It also builds FFI libraries for model methods and exposed functions, initializes chains from fits, runs the example models, and computes LOO with and without moment matching. Tests that need FFI skip when it is unavailable. If CmdStan is not available, PHPUnit skips these tests; CI explicitly installs CmdStan so they execute there.
Comparisons with R
The compatibility suite runs the same algorithms with CmdStanR 1.0 and PHP using identical seeds and precision, then compares individual parameter/log-density draws, approximate densities, descriptive summaries, R-hat, bulk/tail ESS, and MCMC diagnostics. MCMC comparisons include even and odd chain lengths. A hierarchical model with an indexed parameter (shoe_latent[1..12], four chains of 1000 draws) is summarized both ways, by default through stansummary and with measures through the posterior port, and compared with posterior::summarise_draws() to 1e-8 for the mean, median, SD, MAD, a five-point quantile2(), R-hat, and bulk and tail ESS. R is given PHP’s draws as binary doubles rather than reading the CSV files itself, because R’s CSV parsing is not always correctly rounded and an ulp can reorder two near-tied folded draws, which moves R-hat’s tail half by about 1e-6. Its sampler diagnostics are compared with CmdStanR’s diagnostic_summary() on the same CSV files. Further comparisons cover CSV metadata keys, in-process log densities, gradients, Hessians, constrain/unconstrain results, exposed Stan functions, PSIS, LOO, LOO moment matching, WAIC, relative efficiency, and posterior summary measures. Moment matching is compared with loo::loo_moment_match.default() on analytic regression posteriors, both unmodified and with loo’s split step corrected as documented, and with CmdStanR’s loo(moment_match = TRUE) on a same-seed fit. The values behind the figures are compared with R and bayesplot. In the method comparisons, CmdStanR runs PHP’s compiled executable through cmdstan_model(exe_file = ...), so both packages run the same build. R, CmdStanR 1.0, posterior, loo, bayesplot, jsonlite, Rcpp, and RcppEigen are needed. These tests skip when their prerequisites are absent.
The moment-matching reference was first verified with CmdStanR 0.9.0 and two loo 2.10.1.9000 development builds: 7991145, from before stan-dev/loo#405, and fd910fe, which includes it. The isolated R reference in tests/fixtures/moment_match.R records the loo version and commit and contains the explicit split-inverse and two-half efficiency corrections. With an earlier loo, the suite checks that loo’s own split differs where a shift is combined with scaling or a covariance map. With a corrected loo, it checks that loo’s own split matches CmdStanPHP for every design. Transform-order preservation also has an independent regression test.
Limits
Tests use small models and a modest number of draws. They do not constitute a full upstream CmdStanR conformance suite, do not test a real MPI cluster or OpenCL hardware, and do not establish native Windows support. The package’s compatibility contract lists remaining gaps.