pub use crate::fit::{
build_lmm_seam_ws, build_workspace, fit_on, lmm_objective_at, lmm_sweep_fit, lmm_sweep_fit_on,
FitView, FitWorkspace, LmmSeamWs, LmmSweepOutcome,
};
pub use crate::fit::spec_sized_from_ids_pub;
pub use crate::glm::{glm_irls_fit, sigmoid_stable, GlmFitView, GlmScratch, MAX_IRLS_ITERS};
pub use crate::glmm::GlmmFit;
pub use crate::lme::{lme_fit, LmeFitView, LmeScratch, LmeSuffStats};
pub use crate::lmm::{primary_lambda, LmmFit, LmmGroupings, LmmSuffStats};
pub use crate::ols::{
fit_suff_stats_t_sq, ols_contrast_t_sq, OlsFitView, OlsScratch, OlsSuffStats, PANEL_ROWS,
};
#[cfg(test)]
mod tests {
use crate::lmm::{fit_lmm, LmmWorkspace};
use crate::start::StartValues;
use crate::{Family, ModelSpec, ReStructure, Sizing};
use faer::Mat;
fn lcg(state: &mut u64) -> f64 {
*state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(((*state >> 11) as f64) / ((1u64 << 53) as f64)) * 2.0 - 1.0
}
fn hand_dataset() -> (Mat<f64>, Vec<f64>, Vec<u32>) {
let n = 48usize;
let n_clusters = 6usize;
let mut st = 42u64;
let u_c: Vec<f64> = (0..n_clusters).map(|_| 0.6 * lcg(&mut st)).collect();
let mut x = Mat::<f64>::zeros(n, 3);
let mut y = vec![0.0f64; n];
let mut ids = vec![0u32; n];
for i in 0..n {
let c = i % n_clusters;
ids[i] = c as u32;
let x1 = lcg(&mut st);
let x2 = lcg(&mut st);
x[(i, 0)] = 1.0;
x[(i, 1)] = x1;
x[(i, 2)] = x2;
y[i] = 0.5 + 0.4 * x1 - 0.2 * x2 + u_c[c] + 0.8 * lcg(&mut st);
}
(x, y, ids)
}
fn intercept_lmm_spec() -> ModelSpec {
ModelSpec {
family: Family::Gaussian,
re: Some(ReStructure {
sizing: Sizing::FixedClusters { n_clusters: 6 },
slopes: vec![],
extra_groupings: vec![],
}),
}
}
#[test]
fn start_values_theta_threads_into_loop_kernel() {
let (x, y, pid) = hand_dataset();
let (n, p) = (x.nrows(), 3);
let model = intercept_lmm_spec();
let mut ws_cold = LmmWorkspace::for_cluster_spec(p, &model, n, &[]);
ws_cold.suff.reset();
ws_cold.suff.add_rows_multi(x.as_ref(), &y, &pid, &[], None);
let cold = fit_lmm(&mut ws_cold, &[1, 2], None);
let warm_start = StartValues {
beta: vec![0.0; p],
theta: vec![5.0],
};
let mut ws_warm = LmmWorkspace::for_cluster_spec(p, &model, n, &[]);
ws_warm.suff.reset();
ws_warm.suff.add_rows_multi(x.as_ref(), &y, &pid, &[], None);
let warm = fit_lmm(&mut ws_warm, &[1, 2], Some(&warm_start.theta));
assert!(
cold.converged && warm.converged,
"both starts must converge"
);
for j in [1usize, 2] {
let (a, b) = (ws_cold.fit.betas[j], ws_warm.fit.betas[j]);
let d = (a - b).abs();
assert!(
d <= 1e-7 || d <= 1e-6 * a.abs().max(b.abs()),
"loop MLE must be start-independent: β[{j}] cold {a} vs warm {b}"
);
}
}
}