#![cfg(all(feature = "oracle-tests", feature = "formula"))]
mod oracle_support;
use glmm::WaldSe;
use oracle_support::{
align_coefs, assert_abs, assert_coefs, assert_rel, load_golden, refit, refit_with, tol, Golden,
};
use serde_json::Value;
#[test]
fn aligned_dev_default_is_minus_two_loglik() {
let g = load_golden("pastes_lmm");
let ll = g.estimates.loglik.expect("gaussian golden has loglik");
assert_eq!(oracle_support::dev_align::aligned_dev(&g), Some(-2.0 * ll));
}
#[test]
fn aligned_dev_none_when_golden_lacks_loglik() {
let g = load_golden("sim_binomial_slope1_agq_k7");
assert_eq!(oracle_support::dev_align::aligned_dev(&g), None);
}
#[test]
fn dev_align_none_matches_the_six_vector_agq_goldens() {
let expect_none = [
"sim_binomial_slope1_agq_k7",
"sim_binomial_slope1_agq_k11",
"sim_binomial_slope2_agq_k7",
"sim_binomial_slope2_agq_k11",
"sim_poisson_slope1_agq_k7",
"sim_poisson_slope1_agq_k11",
];
for (g, _) in corpus() {
let is_none = oracle_support::dev_align::aligned_dev(&g).is_none();
assert_eq!(
is_none,
expect_none.contains(&g.name.as_str()),
"{}: aligned_dev None-ness disagrees with the pinned set of six",
g.name
);
}
}
fn corpus() -> Vec<(Golden, Vec<String>)> {
let mut all = m3_corpus();
let weights = weights_corpus();
assert_eq!(weights.len(), 15, "weights tier lost a rung");
all.extend(weights);
all
}
fn factors_of(spec: &Value) -> Vec<String> {
spec["factors"]
.as_array()
.map(|a| {
a.iter()
.map(|f| f.as_str().expect("factor name").to_string())
.collect()
})
.unwrap_or_default()
}
fn is_pending(spec: &Value) -> bool {
spec["pending_reference"]
.as_str()
.is_some_and(|r| !r.is_empty())
}
fn m3_corpus() -> Vec<(Golden, Vec<String>)> {
let manifest: Value =
serde_json::from_str(include_str!("../validation/manifest.json")).expect("manifest parses");
let specs = manifest["m3_goldens"]
.as_array()
.expect("manifest has m3_goldens");
specs
.iter()
.filter(|s| !is_pending(s))
.map(|s| {
let name = s["name"].as_str().expect("spec has a name");
let path = format!(
"{}/validation/goldens/{name}.json",
env!("CARGO_MANIFEST_DIR")
);
let raw = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
let mut golden: Golden =
serde_json::from_str(&raw).unwrap_or_else(|e| panic!("{name}: {e}"));
golden.source = path;
(golden, factors_of(s))
})
.collect()
}
fn weights_corpus() -> Vec<(Golden, Vec<String>)> {
let dir = format!("{}/validation", env!("CARGO_MANIFEST_DIR"));
let manifest: Value =
serde_json::from_str(include_str!("../validation/manifest.json")).expect("manifest parses");
manifest["datasets"]
.as_array()
.expect("manifest has datasets")
.iter()
.filter(|s| s["tier"].as_str() == Some("weights"))
.map(|s| {
let name = s["name"].as_str().expect("spec has a name");
let path = format!("{dir}/results/lme4_simulated/{name}.json");
let raw = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
let mut v: Value = serde_json::from_str(&raw).unwrap_or_else(|e| panic!("{name}: {e}"));
let r_formula = s["r_formula"].clone();
let obj = v.as_object_mut().expect("result is an object");
obj.insert("name".into(), s["name"].clone());
obj.insert("data".into(), s["name"].clone());
obj.insert("kind".into(), kind_of(s).into());
obj.insert("r_formula".into(), r_formula);
let mut golden: Golden =
serde_json::from_value(v).unwrap_or_else(|e| panic!("{name}: {e}"));
golden.source = path;
golden.csv = Some(format!("{dir}/data/simulated/{name}.csv"));
golden.weights_col = s["weights_col"].as_str().map(str::to_string);
golden.weights_suite = true;
(golden, factors_of(s))
})
.collect()
}
fn kind_of(spec: &Value) -> &'static str {
let has_re = spec["r_formula"]
.as_str()
.expect("spec has r_formula")
.contains('|');
match (spec["family"].as_str().expect("spec has a family"), has_re) {
("gaussian", _) => "lmm",
(_, true) => "glmm",
(_, false) => "glm",
}
}
#[derive(PartialEq)]
enum Shape {
Lmm,
Glm,
Glmm,
VectorAgq,
WeightedGlm,
}
fn shape_of(g: &Golden) -> Shape {
match (g.kind.as_str(), g.engine.as_str()) {
(_, "GLMMadaptive") => Shape::VectorAgq,
("lmm", _) => Shape::Lmm,
("glm", _) if g.weights_suite => Shape::WeightedGlm,
("glm", _) => Shape::Glm,
("glmm", _) => Shape::Glmm,
(k, _) => panic!("{}: unknown kind {k}", g.name),
}
}
fn asserted_fields(shape: &Shape, g: &Golden) -> Vec<&'static str> {
let mut f = match shape {
Shape::Lmm => vec!["beta", "se", "sigma", "loglik", "varcomp"],
Shape::Glm => vec!["beta", "se", "loglik"],
Shape::WeightedGlm => vec!["beta", "se_rx", "loglik", "varcomp"],
Shape::Glmm => vec!["beta", "se_hessian", "se_rx", "loglik", "varcomp"],
Shape::VectorAgq => vec!["beta", "se_hessian", "varcomp"],
};
if g.estimates.theta.is_some() {
f.push("theta");
}
if g.estimates.dispersion.is_some() {
f.push("dispersion");
}
f
}
fn unasserted_fields(g: &Golden) -> Vec<(&'static str, &'static str)> {
if g.family == "gamma" && g.kind == "glmm" {
return vec![(
"sigma",
"lme4's sigma() on a Gamma glmer is its internal pwrss/n scale \
(0.57258 as a variance on sim_gamma_glmm), which matches neither the \
Pearson moment estimator nor deviance/df.residual. goldens_agq.R \
freezes both it and the Pearson `dispersion` so the in-crate test can \
pick; the crate picked Pearson and reports it as Fit::dispersion, \
which `dispersion` above asserts. glmm has no reported counterpart \
for this second scale. Reviewed and adopted as a deliberate \
divergence 2026-07-21 — see the Gamma GLMM dispersion entry under \
'differences that change the answer' in the crate's lme4 comparison \
notes, which records why the second scale is not exposed on `Fit`. \
Unasserted is not unverified: glmm computes the same pwrss/n \
(`family::glmm_sigma_sq`) and this tier gates it through two derived \
fields it does assert — `se_rx`, which carries σ̂² as its scale factor \
for Gamma, and `varcomp`, which is θ̂²·σ̂² on lme4's VarCorr \
convention. Reading `sigma` here would be a third check on the same \
number.",
)];
}
Vec::new()
}
fn known_open(_g: &Golden) -> Option<&'static str> {
None
}
#[test]
fn all_goldens_are_registered() {
let registered: std::collections::BTreeSet<String> =
m3_corpus().into_iter().map(|(g, _)| g.name).collect();
let dir = format!("{}/validation/goldens", env!("CARGO_MANIFEST_DIR"));
let mut on_disk = Vec::new();
for entry in std::fs::read_dir(&dir).expect("goldens dir") {
let path = entry.expect("dir entry").path();
if path.extension().is_some_and(|e| e == "json") {
on_disk.push(
path.file_stem()
.expect("stem")
.to_string_lossy()
.into_owned(),
);
}
}
on_disk.sort();
let orphans: Vec<&String> = on_disk
.iter()
.filter(|n| !registered.contains(*n))
.collect();
assert!(
orphans.is_empty(),
"goldens with no manifest entry (unregenerable, so not oracles): {orphans:?}"
);
assert_eq!(
on_disk.len(),
registered.len(),
"manifest registers goldens that are not on disk"
);
}
#[test]
fn pending_references_are_absent_and_reasoned() {
let manifest: Value =
serde_json::from_str(include_str!("../validation/manifest.json")).expect("manifest parses");
let pending = pending_specs(
manifest["m3_goldens"]
.as_array()
.expect("manifest has m3_goldens"),
);
if !pending.is_empty() {
println!("m3_goldens awaiting a reference, excluded from Tier 2: {pending:?}");
}
}
fn pending_specs(specs: &[Value]) -> Vec<String> {
let mut pending = Vec::new();
for s in specs {
let name = s["name"].as_str().expect("spec has a name");
let Some(reason) = s["pending_reference"].as_str() else {
continue;
};
assert!(
!reason.is_empty(),
"{name}: pending_reference is present but empty — an exclusion with no reason"
);
let path = format!(
"{}/validation/goldens/{name}.json",
env!("CARGO_MANIFEST_DIR")
);
assert!(
!std::path::Path::new(&path).exists(),
"{name}: pending_reference is set but {path} exists — drop the field, \
or the golden is frozen and gated by nothing"
);
pending.push(name.to_string());
}
pending
}
mod flag_semantics {
use super::{is_pending, pending_specs};
use serde_json::{json, Value};
fn frozen_golden_spec(reason: Value) -> Value {
json!({ "name": "sleepstudy_lmm", "pending_reference": reason })
}
#[test]
fn absent_empty_and_non_empty_reasons() {
assert!(!is_pending(&json!({ "name": "x" })));
assert!(!is_pending(
&json!({ "name": "x", "pending_reference": "" })
));
assert!(is_pending(
&json!({ "name": "x", "pending_reference": "awaiting the R run" })
));
}
#[test]
#[should_panic(expected = "an exclusion with no reason")]
fn empty_reason_is_rejected() {
pending_specs(&[json!({ "name": "not_a_golden", "pending_reference": "" })]);
}
#[test]
#[should_panic(expected = "drop the field")]
fn flag_outliving_the_golden_is_rejected() {
pending_specs(&[frozen_golden_spec(json!("awaiting the R run"))]);
}
#[test]
fn an_unfrozen_flagged_spec_is_reported() {
let pending = pending_specs(&[
json!({ "name": "plain_spec" }),
json!({ "name": "not_a_golden", "pending_reference": "awaiting the R run" }),
]);
assert_eq!(pending, vec!["not_a_golden".to_string()]);
}
}
#[test]
fn golden_fields_are_all_asserted() {
let mut missing = Vec::new();
for (g, _) in corpus() {
let raw = std::fs::read_to_string(&g.source).expect("golden");
let v: Value = serde_json::from_str(&raw).expect("golden parses");
let shape = shape_of(&g);
let covered = asserted_fields(&shape, &g);
let excused = unasserted_fields(&g);
for key in v["estimates"].as_object().expect("estimates object").keys() {
let k = key.as_str();
if !covered.contains(&k) && !excused.iter().any(|(f, _)| *f == k) {
missing.push(format!("{}.{key}", g.name));
}
}
for (field, reason) in &excused {
assert!(
v["estimates"].get(field).is_some(),
"{}: `{field}` is excused but the golden has no such field",
g.name
);
assert!(
!reason.is_empty(),
"{}: `{field}` is excused with no reason",
g.name
);
}
for key in &covered {
assert!(
v["estimates"].get(key).is_some(),
"{}: asserted_fields lists `{key}`, but the golden has no such field",
g.name
);
}
}
assert!(
missing.is_empty(),
"golden fields that no Tier 2 assertion reads: {missing:#?}"
);
}
#[test]
fn goldens_agree_with_the_references() {
assert_eq!(corpus().len(), 65, "the cross-engine corpus changed size");
let mut open = Vec::new();
for (g, factors) in corpus() {
if let Some(reason) = known_open(&g) {
assert!(!reason.is_empty());
open.push(g.name.clone());
continue;
}
let shape = shape_of(&g);
let factor_refs: Vec<&str> = factors.iter().map(String::as_str).collect();
let (f, cols, groups) = refit(&g, &factor_refs);
let name = &g.name;
let align = align_coefs(&cols, &g.coef_names, f.aliased(), name);
assert_eq!(
f.converged(),
g.converged,
"{name}: convergence flag disagrees with the oracle"
);
assert_eq!(
f.singular(),
g.singular,
"{name}: singularity flag disagrees with the oracle"
);
if !g.converged {
continue;
}
let (beta_band, se_band, sd_band) = match shape {
Shape::VectorAgq => (
tol::AGQ_BETA_REL,
tol::AGQ_SE_HESSIAN_REL,
tol::AGQ_STDDEV_REL,
),
_ => (tol::BETA_REL, tol::SE_REL, tol::STDDEV_REL),
};
assert_coefs(
&f.beta,
f.aliased(),
&align,
&g.estimates.beta,
beta_band,
&format!("{name}: beta"),
);
if let Some(se) = &g.estimates.se {
assert_coefs(
&f.se,
f.aliased(),
&align,
se,
se_band,
&format!("{name}: se"),
);
}
if let Some(se) = &g.estimates.se_hessian {
let band = if shape == Shape::VectorAgq {
tol::AGQ_SE_HESSIAN_REL
} else {
tol::SE_HESSIAN_REL
};
assert_coefs(
&f.se,
f.aliased(),
&align,
se,
band,
&format!("{name}: se_hessian"),
);
}
if let Some(se_rx) = &g.estimates.se_rx {
let (fx, _, _) = refit_with(&g, &factor_refs, WaldSe::Rx);
assert_coefs(
&fx.se,
fx.aliased(),
&align,
se_rx,
tol::SE_REL,
&format!("{name}: se_rx"),
);
}
if let (Some(sigma), Shape::Lmm) = (g.estimates.sigma, &shape) {
assert_rel(
f.dispersion.sqrt(),
sigma,
tol::STDDEV_REL,
&format!("{name}: sigma"),
);
}
if let Some(theta) = g.estimates.theta {
assert_rel(
f.dispersion,
theta,
tol::BETA_REL,
&format!("{name}: theta"),
);
}
if let Some(phi) = g.estimates.dispersion {
assert_rel(
f.dispersion,
phi,
tol::BETA_REL,
&format!("{name}: dispersion"),
);
}
match oracle_support::dev_align::aligned_dev(&g) {
None => eprintln!("DEV-NA {name}: golden carries no loglik — parameter sanity only"),
Some(dev_ref) => {
let dev_g = -2.0 * f.loglik;
let d = dev_g - dev_ref;
assert!(
d.abs() <= oracle_support::DEV_BIG,
"{name}: |Δdev|={d:.3e} > DEV_BIG — suspected convention mismatch"
);
assert!(
d <= oracle_support::DEV_EPS,
"{name}: Δdev={d:.3e} > DEV_EPS — worse optimum than reference"
);
}
}
if let Some(varcomp) = &g.estimates.varcomp {
assert_eq!(groups.len(), varcomp.len(), "{name}: grouping count");
for (k, gname) in groups.iter().enumerate() {
let block = g.estimates.block(&oracle_name(gname));
let (sds, corr) = f.stddev_corr(k);
assert_eq!(sds.len(), block.stddev.len(), "{name}/{gname}: block width");
assert_eq!(
block.terms.len(),
sds.len(),
"{name}/{gname}: the oracle names {} RE terms for a {}-wide block",
block.terms.len(),
sds.len()
);
for (t, (&got, &want)) in sds.iter().zip(&block.stddev).enumerate() {
assert_rel(got, want, sd_band, &format!("{name}: {gname} stddev[{t}]"));
}
if let Some(want_corr) = &block.corr {
for a in 0..sds.len() {
for b in (a + 1)..sds.len() {
assert_abs(
corr[a][b],
want_corr[a][b],
tol::AGQ_CORR_ABS,
&format!("{name}: {gname} corr[{a}][{b}]"),
);
}
}
}
}
}
}
assert_open_set_unchanged(&open);
assert_documented_divergences_all_fired();
}
fn assert_documented_divergences_all_fired() {
use oracle_support::divergence;
let reg = divergence::registry();
let fired = reg.fired();
let in_corpus: std::collections::BTreeSet<String> =
corpus().into_iter().map(|(g, _)| g.name).collect();
let mut expected = std::collections::BTreeSet::new();
for e in reg.scoped() {
if !in_corpus.contains(&e.dataset) {
continue; }
expected.insert(e.id.clone());
if fired.contains(&e.id) {
eprintln!(
"documented divergence: {} rung {} [{}] <= {:.1e}\n {}\n direction: {}\n see: {}",
e.dataset,
e.rung,
e.quantities.join(","),
e.max_rel,
e.summary,
e.direction,
e.review
);
}
}
assert_eq!(
fired, expected,
"documented-divergence registry is out of date: entries scoped to this \
tier that no longer fire must be deleted, and a divergence that starts \
firing must be written up first"
);
}
fn assert_open_set_unchanged(open: &[String]) {
assert_eq!(
open,
Vec::<String>::new(),
"the set of goldens Tier 2 cannot gate has changed"
);
}
fn oracle_name(glmm_name: &str) -> String {
match glmm_name.split_once(':') {
Some((outer, inner)) => format!("{inner}:{outer}"),
None => glmm_name.to_string(),
}
}
#[test]
#[ignore]
fn kkt_calibration_measurement() {
for (g, factors) in corpus() {
if !matches!(shape_of(&g), Shape::Glmm | Shape::VectorAgq) {
continue;
}
let factor_refs: Vec<&str> = factors.iter().map(String::as_str).collect();
let (f, _cols, _groups) = refit(&g, &factor_refs);
println!(
"{}\tboundary={:?}\tdeviance={}\tkkt={:e}",
g.name, f.diagnostics.boundary, f.deviance, f.diagnostics.kkt_grad_norm
);
}
let s = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/glmm_hessian_vcov.json"
))
.expect("read hessian fixture");
let v: Value = serde_json::from_str(&s).expect("parse hessian fixture");
let n = v["n"].as_u64().unwrap() as usize;
let x_rows: Vec<Vec<f64>> = v["x"]
.as_array()
.unwrap()
.iter()
.map(|r| {
r.as_array()
.unwrap()
.iter()
.map(|e| e.as_f64().unwrap())
.collect()
})
.collect();
let p = x_rows[0].len();
let x: Vec<f64> = x_rows.iter().flat_map(|r| r.iter().copied()).collect();
let y: Vec<f64> = v["y"]
.as_array()
.unwrap()
.iter()
.map(|e| e.as_f64().unwrap())
.collect();
let ids: Vec<u32> = v["cluster_ids"]
.as_array()
.unwrap()
.iter()
.map(|e| e.as_u64().unwrap() as u32)
.collect();
let n_clusters = ids.iter().max().unwrap() + 1;
let model = glmm::ModelSpec {
family: glmm::Family::Binomial {
link: glmm::BinomialLink::Logit,
},
re: Some(glmm::ReStructure {
sizing: glmm::Sizing::FixedClusters { n_clusters },
slopes: vec![],
extra_groupings: vec![],
}),
};
let opts = glmm::FitOptions {
target_indices: (0..p as u32).collect(),
..glmm::FitOptions::default()
};
let f = glmm::fit_cold(
&x,
&y,
n,
p,
&model,
&glmm::GroupIds {
primary: ids,
extra: vec![],
},
&opts,
);
println!(
"glmm_hessian_vcov_fixture\tboundary={:?}\tdeviance={}\tkkt={:e}",
f.diagnostics.boundary, f.deviance, f.diagnostics.kkt_grad_norm
);
}